Back to Blog

Rails 6.1 adds support for belongs_to to has_many inversing

on 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.

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.


You might also like

If you liked this blog post, check out similar ones from BigBinary