lib/rubocop/cop/rails/duplicate_association.rb in rubocop-rails-2.21.2 vs lib/rubocop/cop/rails/duplicate_association.rb in rubocop-rails-2.22.0
- old
+ new
@@ -18,41 +18,94 @@
#
# # good
# belongs_to :bar
# has_one :foo
#
+ # # bad
+ # belongs_to :foo, class_name: 'Foo'
+ # belongs_to :bar, class_name: 'Foo'
+ # has_one :baz
+ #
+ # # good
+ # belongs_to :bar, class_name: 'Foo'
+ # has_one :foo
+ #
class DuplicateAssociation < Base
include RangeHelp
extend AutoCorrector
include ClassSendNodeHelper
include ActiveRecordHelper
MSG = "Association `%<name>s` is defined multiple times. Don't repeat associations."
+ MSG_CLASS_NAME = "Association `class_name: %<name>s` is defined multiple times. Don't repeat associations."
def_node_matcher :association, <<~PATTERN
- (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) ...)
+ (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) $...)
PATTERN
+ def_node_matcher :class_name, <<~PATTERN
+ (hash (pair (sym :class_name) $_))
+ PATTERN
+
def on_class(class_node)
return unless active_record?(class_node.parent_class)
- offenses(class_node).each do |name, nodes|
- nodes.each do |node|
- add_offense(node, message: format(MSG, name: name)) do |corrector|
- next if same_line?(nodes.last, node)
+ association_nodes = association_nodes(class_node)
- corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
- end
- end
+ duplicated_association_name_nodes(association_nodes).each do |name, nodes|
+ register_offense(name, nodes, MSG)
end
+
+ duplicated_class_name_nodes(association_nodes).each do |class_name, nodes|
+ register_offense(class_name, nodes, MSG_CLASS_NAME)
+ end
end
private
- def offenses(class_node)
- class_send_nodes(class_node).select { |node| association(node) }
- .group_by { |node| association(node).to_sym }
- .select { |_, nodes| nodes.length > 1 }
+ def register_offense(name, nodes, message_template)
+ nodes.each do |node|
+ add_offense(node, message: format(message_template, name: name)) do |corrector|
+ next if same_line?(nodes.last, node)
+
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
+ end
+ end
+ end
+
+ def association_nodes(class_node)
+ class_send_nodes(class_node).select do |node|
+ association(node)&.first
+ end
+ end
+
+ def duplicated_association_name_nodes(association_nodes)
+ grouped_associations = association_nodes.group_by do |node|
+ association(node).first.to_sym
+ end
+
+ leave_duplicated_association(grouped_associations)
+ end
+
+ def duplicated_class_name_nodes(association_nodes)
+ grouped_associations = association_nodes.group_by do |node|
+ arguments = association(node).last
+ next unless arguments.count == 1
+
+ if (class_name = class_name(arguments.first))
+ class_name.source
+ end
+ end
+
+ grouped_associations.delete(nil)
+
+ leave_duplicated_association(grouped_associations)
+ end
+
+ def leave_duplicated_association(grouped_associations)
+ grouped_associations.select do |_, nodes|
+ nodes.length > 1
+ end
end
end
end
end
end