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
5class Book < ApplicationRecord
6  belongs_to :author, inverse_of: :books

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
3  def test_book_inverse_of_author
4    author = Author.new
5    book = author.books.build
7    assert_equal book.author, author
8  end
10  def test_author_inverse_of_book
11    book = Book.new
12    author = book.build_author
14    assert_includes author.books, book
15  end

In previous Rails versions, the test cases would fail.

1# Running:
7Expected #<ActiveRecord::Associations::CollectionProxy []> to include #<Book id: nil, author_id: nil, created_at: nil, updated_at: nil>.
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:
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.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, React.js, React Native, remote work,open source, engineering & design.