Class: RuboCop::Cop::Koi::DuplicatesAssociation

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Defined in:
lib/rubocop/cop/koi/duplicates_association.rb

Overview

Content items are duplicated for copy-on-write editing, and owned detail records must be duplicated with them. katalyst-content provides duplicates_association for this; without it the duplicate silently loses the detail record when the item is next edited.

An association is considered an owned detail when it declares dependent: :destroy and is autosaved, either explicitly with autosave: true or implicitly via accepts_nested_attributes_for.

The cop only applies where duplicates_association is available: classes that extend one of the configured BaseClasses (Katalyst::Content::Item by default), include Katalyst::Content::DuplicatesAssociations, or already call duplicates_association.

Examples:

# bad
class Embed < Katalyst::Content::Item
  has_one :embed_detail, autosave: true, dependent: :destroy
end

# good
class Embed < Katalyst::Content::Item
  has_one :embed_detail, autosave: true, dependent: :destroy
  duplicates_association :embed_detail
end

Constant Summary collapse

MSG =
"Owned associations are duplicated with the item; " \
"declare `duplicates_association :%<name>s`."
RESTRICT_ON_SEND =
%i[has_one has_many].freeze
BASE_CLASSES_DEFAULT =
["Katalyst::Content::Item"].freeze

Instance Method Summary collapse

Instance Method Details

#association(node) ⇒ Object



43
44
45
# File 'lib/rubocop/cop/koi/duplicates_association.rb', line 43

def_node_matcher :association, <<~PATTERN
  (send nil? {:has_one :has_many} (sym $_) $(hash ...))
PATTERN

#duplicates_association_names(node) ⇒ Object



48
49
50
# File 'lib/rubocop/cop/koi/duplicates_association.rb', line 48

def_node_search :duplicates_association_names, <<~PATTERN
  (send nil? :duplicates_association (sym $_)+)
PATTERN

#includes_concern?(node) ⇒ Object



53
54
55
# File 'lib/rubocop/cop/koi/duplicates_association.rb', line 53

def_node_search :includes_concern?, <<~PATTERN
  (send nil? :include (const _ :DuplicatesAssociations))
PATTERN

#nested_attributes(node) ⇒ Object



58
59
60
# File 'lib/rubocop/cop/koi/duplicates_association.rb', line 58

def_node_search :nested_attributes, <<~PATTERN
  $(send nil? :accepts_nested_attributes_for (sym $_) ...)
PATTERN

#on_send(node) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rubocop/cop/koi/duplicates_association.rb', line 62

def on_send(node)
  association(node) do |name, options|
    klass = node.each_ancestor(:class).first
    return unless klass
    return unless owned?(klass, name, options)
    return unless duplication_available?(klass)
    return if declared?(klass, name)

    add_offense(node, message: format(MSG, name:)) do |corrector|
      anchor = nested_attributes_node(klass, name) || node
      corrector.insert_after(anchor, "\n#{' ' * anchor.loc.column}duplicates_association :#{name}")
    end
  end
end