Class: RuboCop::Cop::RSpec::InverseOfMatcher
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::RSpec::InverseOfMatcher
- Defined in:
- lib/rubocop/cop/rspec/inverse_of_matcher.rb
Overview
Ensures that RSpec association specs handle ‘.inverse_of` correctly depending on whether the model is a root (directly `< ApplicationRecord`) or an STI subclass.
Rules:
-
Root models (direct superclass = ‘ApplicationRecord`) must include `.inverse_of`.
-
STI subclasses (superclass is another model) must NOT include ‘.inverse_of` (it belongs to the parent).
-
Associations marked as polymorphic or through are ignored.
-
Classes whose model file is missing or whose superclass is not a model are ignored.
The root/subclass decision is made statically — by reading the model file and parsing its ‘class X < Y` declaration. The cop never loads the model class (a linter runs without the Rails app booted).
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_configuration ⇒ Object
26 27 28 29 30 |
# File 'lib/rubocop/cop/rspec/inverse_of_matcher.rb', line 26 def self.default_configuration super.merge( 'Include' => ['spec/models/**/*_spec.rb'] ) end |
Instance Method Details
#on_send(node) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/rubocop/cop/rspec/inverse_of_matcher.rb', line 32 def on_send(node) return unless node.method?(:belong_to) 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 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/InverseOfMatcher failed on #{source_name}: #{e.}" end |