Rails 7 now allows automatic inverse_of detection for associations with scopes


ActiveRecord has many tricks up its sleeve. One of the many is this seemingly simple association option called inverse_of, which helps us to explicitly declare bi-directional associations.

Let’s have a look!

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end

class Book < ApplicationRecord
  belongs_to :author
end

Seems almost redundant, but it performs a very important function. When inverse_of is declared, one can update related models without explicitly writing update statements.

> author = Author.first
> book = author.books.first
> author.first_name == book.writer.first_name
=> true

> author.first_name = 'David'
> author.first_name == book.writer.first_name
=> true

As you can see, without actually updating the name of the author, inverse_of was able to relate the models.

Another useful element is that inverse_of stores a copy of the relation in the ActiveRecord query. So when you call upon the association, it does not need to load it from the database.

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end

As you can see, the inverse_of filter is now applied to the belongs_to relation.

> author = Author.first
> book = author.books.first
> book.author == author
=> true

However if the option was not applied:

> author = Author.first
> book = author.books.first
> book.author == author
# Author Load (0.1ms) SELECT "authors".* FROM "author" WHERE "author"."id" = 1 LIMIT 1
=> true

Before

Fortunately, Rails adds inverse_of to all belongs_to associations. Unfortunately, it does not do so when a scope is added to the association.

Let’s look at an example:

class Author < ApplicationRecord
  has_many :books, -> { visible }
end

class Book < ApplicationRecord
  belongs_to :author

  scope :visible, -> { where(visible: true) }
  scope :hidden, -> { where(visible: false) }
end

Now let’s perform the queries:

> author = Author.first
> book = author.books.first
> book.author == author
# Author Load (0.4ms)  SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> true

After

Thanks to changes in Rails 7, automatic inverse_of detection for associations with scopes now works!.

Let’s look at the same example again:

> author = Author.first
> book = author.books.first
> book.author == author
=> true

As you can see, no database query was made. This option is not enabled by default in Rails 7. To enable it please set this configuration option,

config.active_record.automatic_scope_inversing = true

Join Our Newsletter