Skip to content

Commit 68013b3

Browse files
committed
Infer :inverse_of for has_many ..., through:
Closes [rails#49574][] Issue rails#49574 outlines how an Active Record join model accessed through a `has_many ..., through:` association is unable to infer an appropriate `:inverse_of` association by pluralizing a predictably pluralizable class name. This commit resolves that issue by also checking a model's reflections for a pluralized inverse name in addition to whatever's provided through the `:as` option or inferred in the singular. The issue shares a code sample: ```ruby ActiveRecord::Schema.define do create_table "listings", force: :cascade do |t| t.bigint "list_id", null: false t.bigint "pin_id", null: false end create_table "lists", force: :cascade do |t| end create_table "pins", force: :cascade do |t| end end class Pin < ActiveRecord::Base has_many :listings end class List < ActiveRecord::Base has_many :listings has_many :pins, through: :listings end class Listing < ActiveRecord::Base belongs_to :list belongs_to :pin end class BugTest < Minitest::Test def test_association_stuff list = List.create! pin = list.pins.new pin.save! assert_equal [pin], list.reload.pins assert_equal 1, list.reload.pins.count end end ``` Unfortunately, there isn't a one-to-one mapping in the test suite's `test/model` directory for this type of structure. The most similar associations were between the `Book`, `Subscription`, and `Subscriber`. For the sake of ease, this commit wraps the test block in a new `skipping_validations_for` helper method to ignore validations declared on the `Subscription` join table model. [rails#49574]: rails#49574
1 parent 310bb2a commit 68013b3

File tree

3 files changed

+15
-2
lines changed

3 files changed

+15
-2
lines changed

activerecord/lib/active_record/reflection.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,10 @@ def inverse_name
708708
def automatic_inverse_of
709709
if can_find_inverse_of_automatically?(self)
710710
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
711+
plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
711712

712713
begin
713-
reflection = klass._reflect_on_association(inverse_name)
714+
reflection = klass._reflect_on_association(inverse_name) || klass._reflect_on_association(plural_inverse_name)
714715
rescue NameError => error
715716
raise unless error.name.to_s == class_name
716717

@@ -720,7 +721,7 @@ def automatic_inverse_of
720721
end
721722

722723
if valid_inverse_reflection?(reflection)
723-
inverse_name
724+
reflection.name
724725
end
725726
end
726727
end

activerecord/test/cases/associations/has_one_through_disable_joins_associations_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require "models/developer"
99
require "models/company"
1010
require "models/computer"
11+
require "models/contract"
1112
require "models/club"
1213
require "models/membership"
1314

activerecord/test/cases/associations/inverse_associations_test.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
require "models/room"
2525
require "models/contract"
2626
require "models/subscription"
27+
require "models/subscriber"
2728
require "models/book"
2829
require "models/branch"
2930
require "models/cpk"
@@ -199,6 +200,16 @@ def test_has_many_and_belongs_to_automatic_inverse_shares_objects_on_comment
199200
assert_equal comment.body, rating.comment.body, "Changing the original Comment's body should change the Comment's body on the association"
200201
end
201202

203+
def test_belongs_to_should_find_inverse_has_many_automatically
204+
book = Book.create!
205+
subscriber = book.subscribers.new nick: "Nickname"
206+
207+
subscriber.save!
208+
209+
assert_equal [subscriber], book.reload.subscribers
210+
assert_equal 1, book.reload.subscribers.count
211+
end
212+
202213
def test_polymorphic_and_has_many_through_relationships_should_not_have_inverses
203214
sponsor_reflection = Sponsor.reflect_on_association(:sponsorable)
204215

0 commit comments

Comments
 (0)