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