Class: RuboCop::Cop::Rails::BidirectionalAssociations

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/rails/bidirectional_associations.rb

Overview

Ensures that all ActiveRecord associations are declared on both sides.

Example:

# bad
class Post < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
end

# good
class Post < ApplicationRecord
  belongs_to :user, inverse_of: :posts
end

class User < ApplicationRecord
  has_many :posts, inverse_of: :user
end

Constant Summary collapse

MSG =
'Associations must be declared on both sides of the relationship.'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.default_configurationObject



31
32
33
34
35
36
# File 'lib/rubocop/cop/rails/bidirectional_associations.rb', line 31

def self.default_configuration
  super.merge(
    'Include' => ['app/models/**/*.rb'],
    'Exclude' => ['app/serializers/**/*.rb']
  )
end

Instance Method Details

#on_new_investigationObject



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rubocop/cop/rails/bidirectional_associations.rb', line 38

def on_new_investigation
  return unless in_model_file?(processed_source.file_path)
  return unless processed_source.ast

  associations.each do |model_name, _assoc_name, inverse_name, target_class|
    next if model_name.nil? || target_class.nil?

    opposite_model_path = File.join(Dir.pwd, "app/models/#{camel_to_snake(target_class)}.rb")
    next unless File.exist?(opposite_model_path)

    opposite_content = File.read(opposite_model_path)

    expected_inverse =
      if inverse_name
        inverse_name.to_s
      else
        camel_to_snake(model_name).split('/').last.pluralize
      end

    unless /(belongs_to|has_many|has_one)\s+:#{expected_inverse}/.match?(opposite_content)
      add_global_offense("#{target_class} is missing opposite association for #{model_name}")
    end
  end
rescue StandardError => e
  source_name =
    if processed_source && processed_source.buffer
      processed_source.file_path
    else
      'unknown'
    end

  warn "Rails/BidirectionalAssociations failed on #{source_name}: #{e.message}"
end