Skip to content

Commit 0d1b246

Browse files
authored
Merge pull request rails#54280 from Shopify/ac-through-assoc-reverts
Revert has-many-through association changes + add regression test
2 parents 1dcfe23 + 4199eeb commit 0d1b246

20 files changed

+68
-256
lines changed

activerecord/lib/active_record/associations/association.rb

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -188,16 +188,6 @@ def extensions
188188
# not reraised. The proxy is \reset and +nil+ is the return value.
189189
def load_target
190190
@target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
191-
if !@target && set_through_target_for_new_record?
192-
reflections = reflection.chain
193-
reflections.pop
194-
reflections.reverse!
195-
196-
@target = reflections.reduce(through_association.target) do |middle_target, through_reflection|
197-
break unless middle_target
198-
middle_target.association(through_reflection.source_reflection_name).load_target
199-
end
200-
end
201191

202192
loaded! unless loaded?
203193
target
@@ -331,10 +321,6 @@ def find_target?
331321
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
332322
end
333323

334-
def set_through_target_for_new_record?
335-
owner.new_record? && reflection.through_reflection? && through_association.target
336-
end
337-
338324
# Returns true if there is a foreign key present on the owner which
339325
# references the target. This is used to determine whether we can load
340326
# the target if the owner is currently a new record (and therefore

activerecord/lib/active_record/associations/collection_association.rb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,6 @@ def include?(record)
272272
def load_target
273273
if find_target?
274274
@target = merge_target_lists(find_target, target)
275-
elsif target.empty? && set_through_target_for_new_record?
276-
reflections = reflection.chain.reverse!
277-
278-
@target = reflections.each_cons(2).reduce(through_association.target) do |middle_target, (middle_reflection, through_reflection)|
279-
if middle_target.nil? || (middle_reflection.collection? && middle_target.empty?)
280-
break []
281-
elsif middle_reflection.collection?
282-
middle_target.flat_map { |record| record.association(through_reflection.source_reflection_name).load_target }.compact
283-
else
284-
middle_target.association(through_reflection.source_reflection_name).load_target
285-
end
286-
end
287275
end
288276

289277
loaded!

activerecord/lib/active_record/autosave_association.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,7 @@ def init_internals
297297
# unless the parent is/was a new record itself.
298298
def associated_records_to_validate_or_save(association, new_record, autosave)
299299
if new_record || custom_validation_context?
300-
target = association && association.target
301-
if target && association.reflection.through_reflection
302-
# We expect new through records to be autosaved by their direct parent.
303-
# This prevents already persisted through records from being validated or saved
304-
# more than once.
305-
target.find_all(&:changed_for_autosave?)
306-
else
307-
target
308-
end
300+
association && association.target
309301
elsif autosave
310302
association.target.find_all(&:changed_for_autosave?)
311303
else

activerecord/lib/active_record/reflection.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,6 @@ def source_reflection
655655
self
656656
end
657657

658-
def source_reflection_name
659-
source_reflection.name
660-
end
661-
662658
# A chain of reflections from this one back to the owner. For more see the explanation in
663659
# ThroughReflection.
664660
def collect_join_chain
@@ -1235,7 +1231,7 @@ def derive_class_name
12351231

12361232
class PolymorphicReflection < AbstractReflection # :nodoc:
12371233
delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key,
1238-
:name, :scope_for, :collection?, to: :@reflection
1234+
:name, :scope_for, to: :@reflection
12391235

12401236
def initialize(reflection, previous_reflection)
12411237
super()

activerecord/test/cases/associations/has_many_through_associations_test.rb

Lines changed: 18 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
require "models/member"
3030
require "models/membership"
3131
require "models/club"
32+
require "models/program"
33+
require "models/program_offering"
34+
require "models/enrollment"
3235
require "models/organization"
3336
require "models/user"
3437
require "models/family"
@@ -41,7 +44,6 @@
4144
require "models/zine"
4245
require "models/interest"
4346
require "models/human"
44-
require "models/account"
4547

4648
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
4749
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
@@ -57,122 +59,6 @@ def setup
5759
Reader.create person_id: 0, post_id: 0
5860
end
5961

60-
def test_setting_has_many_through_one_association_on_new_record_sets_through_records
61-
account_1, account_2 = Account.create!(credit_limit: 100), Account.create!(credit_limit: 100)
62-
firm = Firm.new(accounts: [account_1, account_2])
63-
client = Client.new
64-
client.firm = firm
65-
66-
assert_predicate account_1, :persisted?
67-
assert_predicate account_2, :persisted?
68-
assert_predicate client, :new_record?
69-
assert_predicate client.firm, :new_record?
70-
assert_no_queries { assert_equal [account_1, account_2].sort, client.accounts.sort }
71-
end
72-
73-
def test_setting_has_many_through_many_association_on_new_record_sets_through_records
74-
subscriber_1, subscriber_2 = Subscriber.create!(nick: "nick 1"), Subscriber.create!(nick: "nick 2")
75-
subscription_1 = Subscription.new(subscriber: subscriber_1)
76-
subscription_2 = Subscription.new(subscriber: subscriber_2)
77-
book = Book.new
78-
book.subscriptions = [subscription_1, subscription_2]
79-
80-
assert_predicate subscriber_1, :persisted?
81-
assert_predicate subscriber_2, :persisted?
82-
assert_predicate book, :new_record?
83-
book.subscriptions.each { |subscription| assert_predicate subscription, :new_record? }
84-
assert_no_queries { assert_equal [subscriber_1, subscriber_2].sort, book.subscribers.sort }
85-
end
86-
87-
def test_setting_has_many_through_many_association_with_missing_targets_on_new_record_sets_empty_through_records
88-
subscription_1 = Subscription.new
89-
subscription_2 = Subscription.new
90-
book = Book.new
91-
book.subscriptions = [subscription_1, subscription_2]
92-
93-
assert_predicate book, :new_record?
94-
book.subscriptions.each { |subscription| assert_predicate subscription, :new_record? }
95-
assert_no_queries { assert_equal [], book.subscribers }
96-
end
97-
98-
def test_setting_has_many_through_many_association_with_partial_missing_targets_on_new_record_sets_partial_through_records
99-
subscriber_1 = Subscriber.create!(nick: "nick 1")
100-
subscription_1 = Subscription.new(subscriber: subscriber_1)
101-
subscription_2 = Subscription.new
102-
book = Book.new
103-
book.subscriptions = [subscription_1, subscription_2]
104-
105-
assert_predicate subscriber_1, :persisted?
106-
assert_predicate book, :new_record?
107-
book.subscriptions.each { |subscription| assert_predicate subscription, :new_record? }
108-
assert_no_queries { assert_equal [subscriber_1], book.subscribers }
109-
end
110-
111-
def test_setting_polymorphic_has_many_through_many_association_on_new_record_sets_through_records
112-
human_1, human_2 = Human.create!, Human.create!
113-
interest_1 = Interest.new(polymorphic_human: human_1)
114-
interest_2 = Interest.new(polymorphic_human: human_2)
115-
zine = Zine.new
116-
zine.interests = [interest_1, interest_2]
117-
118-
assert_predicate human_1, :persisted?
119-
assert_predicate human_2, :persisted?
120-
assert_predicate zine, :new_record?
121-
zine.interests.each { |interest| assert_predicate interest, :new_record? }
122-
assert_no_queries { assert_equal [human_1, human_2].sort, zine.polymorphic_humans.sort }
123-
end
124-
125-
def test_setting_nested_has_many_through_one_association_on_new_record_sets_nested_through_records
126-
post_tagging_1, post_tagging_2 = Tagging.create!, Tagging.create!
127-
post = Post.create!(title: "Tagged", body: "Post", taggings: [post_tagging_1, post_tagging_2])
128-
author = Author.new(name: "Josh")
129-
author.posts = [post]
130-
categorization = Categorization.new
131-
categorization.author = author
132-
133-
assert_predicate post_tagging_1, :persisted?
134-
assert_predicate post_tagging_2, :persisted?
135-
assert_predicate post, :persisted?
136-
assert_predicate categorization, :new_record?
137-
assert_predicate categorization.author, :new_record?
138-
assert_no_queries { assert_equal [post_tagging_1, post_tagging_2].sort, categorization.post_taggings.sort }
139-
end
140-
141-
def test_setting_nested_has_many_through_one_association_on_new_record_sets_targetless_nested_through_records
142-
post = Post.create!(title: "Tagged", body: "Post")
143-
post_tagging_1, post_tagging_2 = Tagging.create!(taggable: post), Tagging.create!(taggable: post)
144-
author = Author.new(name: "Josh")
145-
author.posts = [post]
146-
categorization = Categorization.new
147-
categorization.author = author
148-
149-
assert_predicate post_tagging_1, :persisted?
150-
assert_predicate post_tagging_2, :persisted?
151-
assert_predicate post, :persisted?
152-
assert_predicate categorization, :new_record?
153-
assert_predicate categorization.author, :new_record?
154-
assert_queries_count(1) { assert_equal [post_tagging_1, post_tagging_2].sort, categorization.post_taggings.sort }
155-
end
156-
157-
def test_setting_nested_has_many_through_many_association_on_new_record_sets_nested_through_records
158-
account_1 = Account.create!(firm_name: "account 1", credit_limit: 100)
159-
subscriber_1 = Subscriber.create!(nick: "nick 1", account: account_1)
160-
account_2 = Account.create!(firm_name: "account 2", credit_limit: 100)
161-
subscriber_2 = Subscriber.create!(nick: "nick 2", account: account_2)
162-
subscription_1 = Subscription.new(subscriber: subscriber_1)
163-
subscription_2 = Subscription.new(subscriber: subscriber_2)
164-
book = Book.new
165-
book.subscriptions = [subscription_1, subscription_2]
166-
167-
assert_predicate subscriber_1, :persisted?
168-
assert_predicate subscriber_2, :persisted?
169-
assert_predicate account_1, :persisted?
170-
assert_predicate account_2, :persisted?
171-
assert_predicate book, :new_record?
172-
book.subscriptions.each { |subscription| assert_predicate subscription, :new_record? }
173-
assert_no_queries { assert_equal [account_1, account_2].sort, book.subscriber_accounts.sort }
174-
end
175-
17662
def test_has_many_through_create_record
17763
assert books(:awdr).subscribers.create!(nick: "bob")
17864
end
@@ -1665,6 +1551,21 @@ def test_has_many_through_update_ids_with_conditions
16651551
assert_equal [], author.nonspecial_categories_with_condition_ids
16661552
end
16671553

1554+
def test_has_many_through_from_same_parent_to_same_child_creates_join_models
1555+
club = Club.new(name: "Awesome Rails Club")
1556+
member = club.simple_members.build(name: "Jane Doe")
1557+
1558+
program = Program.new(name: "Learn Ruby on Rails")
1559+
program.members << member
1560+
1561+
club.programs << program
1562+
1563+
club.save!
1564+
1565+
assert_equal(1, program.enrollments.size)
1566+
assert_equal(1, club.simple_memberships.size)
1567+
end
1568+
16681569
def test_single_has_many_through_association_with_unpersisted_parent_instance
16691570
post_with_single_has_many_through = Class.new(Post) do
16701571
def self.name; "PostWithSingleHasManyThrough"; end

activerecord/test/cases/associations/has_one_through_associations_test.rb

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
require "models/shop_account"
2424
require "models/customer_carrier"
2525
require "models/cpk"
26-
require "models/rating"
2726

2827
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
2928
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
@@ -44,63 +43,6 @@ def test_has_one_through_executes_limited_query
4443
end
4544
end
4645

47-
def test_setting_association_on_new_record_sets_through_record
48-
club = Club.create!
49-
membership = CurrentMembership.new(club: club)
50-
member = Member.new
51-
member.current_membership = membership
52-
53-
assert_predicate club, :persisted?
54-
assert_predicate member, :new_record?
55-
assert_predicate member.current_membership, :new_record?
56-
assert_no_queries { assert_equal club, member.club }
57-
end
58-
59-
def test_setting_association_on_new_record_sets_nested_through_record
60-
category = Category.create!(name: "General")
61-
club = Club.create!(category: category)
62-
membership = CurrentMembership.new(club: club)
63-
member = Member.new
64-
member.current_membership = membership
65-
66-
assert_predicate category, :persisted?
67-
assert_predicate club, :persisted?
68-
assert_predicate member, :new_record?
69-
assert_predicate member.current_membership, :new_record?
70-
assert_no_queries { assert_equal club.category, member.club_category }
71-
end
72-
73-
def test_setting_association_on_new_record_sets_not_loaded_nested_through_record
74-
category = Category.create!(name: "General")
75-
club = Club.create!(category: category)
76-
club.reload
77-
membership = CurrentMembership.new(club: club)
78-
member = Member.new
79-
member.current_membership = membership
80-
81-
assert_predicate category, :persisted?
82-
assert_predicate club, :persisted?
83-
assert_predicate member, :new_record?
84-
assert_predicate member.current_membership, :new_record?
85-
assert_queries_count(1) { assert_equal club.category, member.club_category }
86-
end
87-
88-
def test_setting_association_on_new_record_sets_nested_through_record_with_belongs_to
89-
essay = Essay.create!
90-
author = Author.create!(name: "Josh", owned_essay: essay)
91-
post = Post.create!(title: "Foo", body: "Bar", author: author)
92-
comment = Comment.new(post: post)
93-
rating = Rating.new
94-
rating.comment = comment
95-
96-
assert_predicate essay, :persisted?
97-
assert_predicate author, :persisted?
98-
assert_predicate post, :persisted?
99-
assert_predicate rating, :new_record?
100-
assert_predicate rating.comment, :new_record?
101-
assert_no_queries { assert_equal essay, rating.owned_essay }
102-
end
103-
10446
def test_creating_association_creates_through_record
10547
new_member = Member.create(name: "Chris")
10648
new_member.club = Club.create(name: "LRUG")

activerecord/test/cases/autosave_association_test.rb

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@
4040
require "models/cake_designer"
4141
require "models/drink_designer"
4242
require "models/cpk"
43-
require "models/family"
44-
require "models/family_tree"
45-
require "models/user"
4643

4744
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
4845
def test_autosave_works_even_when_other_callbacks_update_the_parent_model
@@ -1150,34 +1147,6 @@ def test_autosave_new_record_with_after_create_callback_and_habtm_association
11501147

11511148
assert_equal 1, post.categories.reload.length
11521149
end
1153-
1154-
FamilyLoadingMiddleAndThroughRecordsBeforeSave = Class.new(Family) do
1155-
before_save do
1156-
family_trees.map(&:member) + members
1157-
end
1158-
end
1159-
1160-
def test_autosave_new_record_with_hmt_and_middle_record_built_by_parent
1161-
family = FamilyLoadingMiddleAndThroughRecordsBeforeSave.new
1162-
family_tree = family.family_trees.build
1163-
family_tree.build_member
1164-
family.save!
1165-
family.reload
1166-
1167-
assert_equal 1, family.family_trees.size
1168-
assert_equal 1, family.members.size
1169-
end
1170-
1171-
def test_autosave_new_record_with_hmt_and_middle_record_built_by_through_record
1172-
family = FamilyLoadingMiddleAndThroughRecordsBeforeSave.new
1173-
member = family.members.build
1174-
family.family_trees.build(member: member)
1175-
family.save!
1176-
family.reload
1177-
1178-
assert_equal 1, family.family_trees.size
1179-
assert_equal 1, family.members.size
1180-
end
11811150
end
11821151

11831152
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase

activerecord/test/models/book.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class Book < ActiveRecord::Base
99

1010
has_many :subscriptions
1111
has_many :subscribers, through: :subscriptions
12-
has_many :subscriber_accounts, through: :subscribers, source: :account
1312

1413
has_one :essay
1514

activerecord/test/models/club.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class Club < ActiveRecord::Base
1515

1616
scope :general, -> { left_joins(:category).where(categories: { name: "General" }).unscope(:limit) }
1717

18+
has_many :program_offerings
19+
has_many :programs, through: :program_offerings
20+
21+
has_many :simple_memberships, class_name: "Membership"
22+
has_many :simple_members, through: :simple_memberships, foreign_key: "member_id"
23+
1824
accepts_nested_attributes_for :membership
1925

2026
private
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
class Enrollment < ActiveRecord::Base
4+
belongs_to :program
5+
belongs_to :member, class_name: "SimpleMember"
6+
end

0 commit comments

Comments
 (0)