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.
1class Author < ApplicationRecord 2 has_many :books, inverse_of: :author 3end 4 5class Book < ApplicationRecord 6 belongs_to :author, inverse_of: :books 7end
Before Rails 6.1
has_many to belongs_to inversing
1irb(main):001:0> author = Author.new 2irb(main):002:0> book = author.books.build 3irb(main):003:0> author == book.author 4=> 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.
belongs_to to has_many inversing
1irb(main):001:0> book = Book.new 2irb(main):002:0> author = book.build_author 3irb(main):003:0> author.books 4=> #<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.
After changes in Rails 6.1
The belongs_to inversing can now be traversed in the same way as the has_many inversing.
1irb(main):001:0> book = Book.new 2irb(main):002:0> author = book.build_author 3irb(main):003:0> author.books 4=> #<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.
1class InverseTest < ActiveSupport::TestCase 2 3 def test_book_inverse_of_author 4 author = Author.new 5 book = author.books.build 6 7 assert_equal book.author, author 8 end 9 10 def test_author_inverse_of_book 11 book = Book.new 12 author = book.build_author 13 14 assert_includes author.books, book 15 end 16end
In previous Rails versions, the test cases would fail.
1# Running: 2 3.F 4Failure: 5InverseTest#test_author_inverse_of_book 6 7Expected #<ActiveRecord::Associations::CollectionProxy > to include #<Book id: nil, author_id: nil, created_at: nil, updated_at: nil>. 8 9Finished in 0.292532s, 6.8369 runs/s, 10.2553 assertions/s. 102 runs, 3 assertions, 1 failures, 0 errors, 0 skips
In Rails 6.1, both the tests will pass.
1# Running: 2 3.. 4 5Finished in 0.317668s, 6.2959 runs/s, 9.4438 assertions/s. 62 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Check out this pull request for more details.