Rails 6.1 adds support for belongs_to to has_many inversing

Siddharth Shringi

Siddharth Shringi

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

Before Rails 6.1

has_many to belongs_to inversing

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.

belongs_to to 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 []>

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.

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.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.