Rails 6.1 adds support for belongs_to to has_many inversing

Siddharth Shringi

By Siddharth Shringi

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.