Add a rubocop enforcing inverse_of for ActiveRecord associations

Description of the proposal

This is something of a follow-up to #31851 (closed)

Given a pair of models like:

class Foo < ApplicationRecord
  has_many :bars
end

class Bar < ApplicationRecord
  belongs_to :foo
end

AR will attempt to automatically infer inverse_of: :foo and inverse_of: :bars on each association: https://makandracards.com/makandra/33309-rails-4-1+-automatically-detects-the-inverse_of-an-association . The effect of this is:

foo = Foo.first
bar = foo.bars.first

foo.object_id == bar.foo.object_id

However, sometimes this automatic inference fails, and AR falls back to the default behaviour of doing additional database lookups (and so creating duplicate objects) when traversing the graph. We don't get any notification when this happens.

To me, this suggests we should introduce a cop that tells us to add inverse_of when required. This cop could alert whenever one of a list of known bad attributes is specified to a belongs_to or has_* call, or for simplicity of implementation, we could start requiring inverse_of on all of these calls - a cop already exists, it seems: https://www.rubydoc.info/gems/rubocop/0.61.1/RuboCop/Cop/Rails/InverseOf

It's possible some existing code relies on the implicit reload where inverse_of is not set at the moment - particularly in specs - so we'd probably want to start by adding exceptions for all existing associations, and only enforcing it on newly added ones.

  • Mention the proposal in the next backend weekly call and the #backend channel to encourage contribution
  • Proceed with the proposal once 50% of the maintainers have weighed in, and 80% of the votes are 👍
  • Once approved, mention it again in the next backend weekly call and the #backend channel

/cc @gitlab-org/maintainers/rails-backend

Edited by 🤖 GitLab Bot 🤖