class RuboCop::Cop::Rails::InverseOf
end
has_many :posts, -> { order(published_at: :desc) }
class Blog < ApplicationRecord
# good
@example IgnoreScopes: true
end
has_many :posts, -> { order(published_at: :desc) }
class Blog < ApplicationRecord
# bad
@example IgnoreScopes: false (default)
end
has_many :physicians, through: :appointments
has_many :appointments
class Patient < ApplicationRecord
end
belongs_to :patient, inverse_of: :appointments
belongs_to :physician, inverse_of: :appointments
class Appointment < ApplicationRecord
end
has_many :patients, through: :appointments
has_many :appointments
class Physician < ApplicationRecord
# good
end
has_many :physicians, through: :appointments
has_many :appointments
class Patient < ApplicationRecord
end
belongs_to :patient
belongs_to :physician
class Appointment < ApplicationRecord
end
has_many :patients, through: :appointments
has_many :appointments
class Physician < ApplicationRecord
# However, RuboCop can not detect this pattern…
# bad
@example
end
has_many :pictures, as: :imageable, inverse_of: :imageable
class Product < ApplicationRecord
end
has_many :pictures, as: :imageable, inverse_of: :imageable
class Employee < ApplicationRecord
end
belongs_to :imageable, polymorphic: true
class Picture < ApplicationRecord
# good
end
has_many :pictures, as: :imageable
class Product < ApplicationRecord
end
has_many :pictures, as: :imageable
class Employee < ApplicationRecord
end
belongs_to :imageable, polymorphic: true
class Picture < ApplicationRecord
# bad
@example
end
inverse_of: false)
-> { order(published_at: :desc) },
has_many(:posts,
class Blog < ApplicationRecord
# When you don’t want to use the inverse association.
# good
end
belongs_to :blog
class Post < ApplicationRecord
end
end
has_many :posts, -> { order(published_at: :desc) }
with_options inverse_of: :blog do
class Blog < ApplicationRecord
# good
end
belongs_to :blog
class Post < ApplicationRecord
end
inverse_of: :blog)
-> { order(published_at: :desc) },
has_many(:posts,
class Blog < ApplicationRecord
# good
end
belongs_to :blog
class Post < ApplicationRecord
end
has_many :posts, -> { order(published_at: :desc) }
class Blog < ApplicationRecord
# bad
@example
end
belongs_to :blog
class Post < ApplicationRecord
end
has_many :posts
class Blog < ApplicationRecord
# good
@example
inverse automatically, and is not considered a valid value for this.
setting ‘nil` does not stop Active Record from trying to determine the
associated object in memory, or set to `false` to opt-out. Note that
`:inverse_of` must be manually specified for Active Record to use the
the database twice.
with `blog.posts.first.blog` would cause the `blog` to be loaded from
example below, traversing the a Blog’s association in both directions
because of a scope or the options used. Using the blog with order scope
Active Record can’t automatically determine the inverse association
Looks for has_(one|many) and belongs_to associations where
def ignore_scopes?
def ignore_scopes? cop_config['IgnoreScopes'] == true end
def message(options)
def message(options) if options.any? { |opt| inverse_of_nil_option?(opt) } NIL_MSG else SPECIFY_MSG end end
def on_send(node)
def on_send(node) recv, arguments = association_recv_arguments(node) return unless arguments with_options = with_options_arguments(recv, node) options = arguments.concat(with_options).flat_map do |arg| options_from_argument(arg) end return if options_ignoring_inverse_of?(options) return unless scope?(arguments) || options_requiring_inverse_of?(options) return if options_contain_inverse_of?(options) add_offense(node.loc.selector, message: message(options)) end
def options_contain_inverse_of?(options)
def options_contain_inverse_of?(options) options.any? { |opt| inverse_of_option?(opt) } end
def options_ignoring_inverse_of?(options)
def options_ignoring_inverse_of?(options) options.any? do |opt| through_option?(opt) || polymorphic_option?(opt) end end
def options_requiring_inverse_of?(options)
def options_requiring_inverse_of?(options) required = options.any? do |opt| conditions_option?(opt) || foreign_key_option?(opt) end return required if target_rails_version >= 5.2 required || options.any? { |opt| as_option?(opt) } end
def same_context_in_with_options?(arg, recv)
def same_context_in_with_options?(arg, recv) return true if arg.nil? && recv.nil? arg && recv && arg.children[0] == recv.children[0] end
def scope?(arguments)
def scope?(arguments) !ignore_scopes? && arguments.any?(&:block_type?) end
def with_options_arguments(recv, node)
def with_options_arguments(recv, node) blocks = node.each_ancestor(:block).select do |block| block.send_node.command?(:with_options) && same_context_in_with_options?(block.first_argument, recv) end blocks.flat_map { |n| n.send_node.arguments } end