class RuboCop::Cop::Rails::DuplicateAssociation
has_one :foo
has_many :bar, class_name: ‘Foo’
# good
has_one :baz
has_many :bar, class_name: ‘Foo’
has_many :foo, class_name: ‘Foo’
# bad
has_one :foo
belongs_to :bar
# good
has_one :foo
belongs_to :bar
belongs_to :foo
# bad
@example
simply keeps the last of any duplicates and discards the rest.
previously defined association with the new one. Because of this, this cop’s autocorrection
When an association is defined multiple times on a model, Active Record overrides the
Looks for associations that have been defined multiple times in the same file.
def association_nodes(class_node)
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)
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)
def duplicated_class_name_nodes(association_nodes) filtered_nodes = association_nodes.reject { |node| node.method?(:belongs_to) } grouped_associations = filtered_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)
def leave_duplicated_association(grouped_associations) grouped_associations.select do |_, nodes| nodes.length > 1 end end
def on_class(class_node)
def on_class(class_node) return unless active_record?(class_node.parent_class) association_nodes = association_nodes(class_node) 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
def register_offense(name, nodes, message_template)
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