Class: RuboCop::Cop::RSpec::AssociationInverseOf

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/rspec/association_inverse_of.rb

Overview

Ensures that RSpec association specs handle ‘.inverse_of` correctly depending on whether the model is a root (directly < ApplicationRecord) or a subclass (STI).

Rules:

  • Root models (direct superclass = ApplicationRecord) must include ‘.inverse_of`.

  • Subclasses (STI, superclass != ApplicationRecord but still inherit indirectly) must NOT include ‘.inverse_of`.

  • Associations marked as polymorphic or through are ignored.

  • Non-ActiveRecord classes are ignored.

Constant Summary collapse

MSG_MISSING_INVERSE =
'Root models must include `.inverse_of` in association specs.'
MSG_FORBIDDEN_INVERSE =
'Subclasses must NOT include `.inverse_of` in specs (it belongs to the parent).'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.default_configurationObject



22
23
24
25
26
# File 'lib/rubocop/cop/rspec/association_inverse_of.rb', line 22

def self.default_configuration
  super.merge(
    'Include' => ['spec/models/**/*_spec.rb']
  )
end

Instance Method Details

#on_send(node) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/rubocop/cop/rspec/association_inverse_of.rb', line 28

def on_send(node)
  return unless node.method?(:belong_to)

  # Skip polymorphic or through associations
  return if chain_has_option?(node, :polymorphic)
  return if chain_has_option?(node, :through)

  model_name = model_class_from_spec
  return unless model_name

  classification = classify_model(model_name)
  return if classification == :non_ar # ignore non-ActiveRecord classes

  if classification == :root
    add_offense(node.loc.selector, message: MSG_MISSING_INVERSE) unless chain_has_inverse_of?(node)
  elsif classification == :subclass
    add_offense(node.loc.selector, message: MSG_FORBIDDEN_INVERSE) if chain_has_inverse_of?(node)
  end
rescue StandardError => e
  source_name =
    if processed_source && processed_source.buffer
      processed_source.file_path
    else
      'unknown'
    end

  warn "RSpec/AssociationInverseOf failed on #{source_name}: #{e.message}"
end