January 19, 2021
This blog is part of our Rails 6.1 series.
Before Rails 6.1, we could only traverse the object chain in one direction - from has_many to belongs_to. Now we can traverse the chain bi-directionally.
The inverse_of
option, both in belongs_to
and has_many
is
used to specify the name of the inverse association.
Let's see an example.
class Author < ApplicationRecord
has_many :books, inverse_of: :author
end
class Book < ApplicationRecord
belongs_to :author, inverse_of: :books
end
irb(main):001:0> author = Author.new
irb(main):002:0> book = author.books.build
irb(main):003:0> author == book.author
=> true
In the above code,
first we created the author
and then
a book
instance through the has_many
association.
In line 3,
we traverse the object chain
back to the author using the belongs_to
association method
on the book instance.
irb(main):001:0> book = Book.new
irb(main):002:0> author = book.build_author
irb(main):003:0> author.books
=> #<ActiveRecord::Associations::CollectionProxy []>
In the above case,
we created the book
instance and then
we created the author
instance using
the method added by belongs_to
association.
But when we tried to traverse the object chain
through the has_many
association,
we got an empty collection
instead of one with the book
instance.
The belongs_to
inversing can now be traversed
in the same way as the has_many
inversing.
irb(main):001:0> book = Book.new
irb(main):002:0> author = book.build_author
irb(main):003:0> author.books
=> #<ActiveRecord::Associations::CollectionProxy [#<Book id: nil, author_id: nil, created_at: nil, updated_at: nil>]>
Here we get the collection with the book
instance
instead of an empty collection.
We can also verify using a test.
class InverseTest < ActiveSupport::TestCase
def test_book_inverse_of_author
author = Author.new
book = author.books.build
assert_equal book.author, author
end
def test_author_inverse_of_book
book = Book.new
author = book.build_author
assert_includes author.books, book
end
end
In previous Rails versions, the test cases would fail.
# Running:
.F
Failure:
InverseTest#test_author_inverse_of_book
Expected #<ActiveRecord::Associations::CollectionProxy []> to include #<Book id: nil, author_id: nil, created_at: nil, updated_at: nil>.
Finished in 0.292532s, 6.8369 runs/s, 10.2553 assertions/s.
2 runs, 3 assertions, 1 failures, 0 errors, 0 skips
In Rails 6.1, both the tests will pass.
# Running:
..
Finished in 0.317668s, 6.2959 runs/s, 9.4438 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Check out this pull request for more details.
If this blog was helpful, check out our full blog archive.