Rails 6.1 adds support for belongs_to to has_many inversing

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.

Related blogs

Follow @bigbinary on X. Check out our full blog archive.