Skip to content

Commit 75ef18c

Browse files
robotdanarafaelfranca
authored andcommitted
Can preload associations through polymorphic associations
1 parent 7bdfc63 commit 75ef18c

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Add support to preload associations of polymorphic associations when not all the records have the requested associations.
2+
3+
*Dana Sherson*
4+
15
* Add `touch_all` method to `ActiveRecord::Relation`.
26

37
Example:

activerecord/lib/active_record/associations/preloader.rb

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,26 +98,30 @@ def preload(records, associations, preload_scope = nil)
9898
private
9999

100100
# Loads all the given data into +records+ for the +association+.
101-
def preloaders_on(association, records, scope)
101+
def preloaders_on(association, records, scope, polymorphic_parent = false)
102102
case association
103103
when Hash
104-
preloaders_for_hash(association, records, scope)
104+
preloaders_for_hash(association, records, scope, polymorphic_parent)
105105
when Symbol
106-
preloaders_for_one(association, records, scope)
106+
preloaders_for_one(association, records, scope, polymorphic_parent)
107107
when String
108-
preloaders_for_one(association.to_sym, records, scope)
108+
preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
109109
else
110110
raise ArgumentError, "#{association.inspect} was not recognized for preload"
111111
end
112112
end
113113

114-
def preloaders_for_hash(association, records, scope)
114+
def preloaders_for_hash(association, records, scope, polymorphic_parent)
115115
association.flat_map { |parent, child|
116-
loaders = preloaders_for_one parent, records, scope
116+
loaders = preloaders_for_one parent, records, scope, polymorphic_parent
117117

118118
recs = loaders.flat_map(&:preloaded_records).uniq
119+
120+
reflection = records.first.class._reflect_on_association(parent)
121+
polymorphic_parent = reflection && reflection.options[:polymorphic]
122+
119123
loaders.concat Array.wrap(child).flat_map { |assoc|
120-
preloaders_on assoc, recs, scope
124+
preloaders_on assoc, recs, scope, polymorphic_parent
121125
}
122126
loaders
123127
}
@@ -135,8 +139,8 @@ def preloaders_for_hash(association, records, scope)
135139
# Additionally, polymorphic belongs_to associations can have multiple associated
136140
# classes, depending on the polymorphic_type field. So we group by the classes as
137141
# well.
138-
def preloaders_for_one(association, records, scope)
139-
grouped_records(association, records).flat_map do |reflection, klasses|
142+
def preloaders_for_one(association, records, scope, polymorphic_parent)
143+
grouped_records(association, records, polymorphic_parent).flat_map do |reflection, klasses|
140144
klasses.map do |rhs_klass, rs|
141145
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
142146
loader.run self
@@ -145,10 +149,11 @@ def preloaders_for_one(association, records, scope)
145149
end
146150
end
147151

148-
def grouped_records(association, records)
152+
def grouped_records(association, records, polymorphic_parent)
149153
h = {}
150154
records.each do |record|
151155
next unless record
156+
next if polymorphic_parent && !record.class._reflect_on_association(association)
152157
assoc = record.association(association)
153158
next unless assoc.klass
154159
klasses = h[assoc.reflection] ||= {}

activerecord/test/cases/associations/eager_test.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,35 @@ def test_preloading_has_many_through_with_custom_scope
15151515
Author.preload(:readonly_comments).first!
15161516
end
15171517

1518+
test "preloading through a polymorphic association doesn't require the association to exist" do
1519+
sponsors = []
1520+
assert_queries 5 do
1521+
sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [:post, :membership]).to_a
1522+
end
1523+
# check the preload worked
1524+
assert_queries 0 do
1525+
sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
1526+
end
1527+
end
1528+
1529+
test "preloading a regular association through a polymorphic association doesn't require the association to exist on all types" do
1530+
sponsors = []
1531+
assert_queries 6 do
1532+
sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :first_comment }, :membership]).to_a
1533+
end
1534+
# check the preload worked
1535+
assert_queries 0 do
1536+
sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
1537+
end
1538+
end
1539+
1540+
test "preloading a regular association with a typo through a polymorphic association still raises" do
1541+
# this test contains an intentional typo of first -> fist
1542+
assert_raises(ActiveRecord::AssociationNotFoundError) do
1543+
Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :fist_comment }, :membership]).to_a
1544+
end
1545+
end
1546+
15181547
private
15191548
def find_all_ordered(klass, include = nil)
15201549
klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a

activerecord/test/fixtures/sponsors.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ crazy_club_sponsor_for_groucho:
1010
sponsor_club: crazy_club
1111
sponsorable_id: 3
1212
sponsorable_type: Member
13+
sponsor_for_author_david:
14+
sponsorable_id: 1
15+
sponsorable_type: Author

0 commit comments

Comments
 (0)