Skip to content
Permalink
Browse files
Can preload associations through polymorphic associations
  • Loading branch information
robotdana authored and rafaelfranca committed Apr 20, 2018
1 parent 7bdfc63 commit 75ef18c67c29b1b51314b6c8a963cee53394080b
@@ -1,3 +1,7 @@
* Add support to preload associations of polymorphic associations when not all the records have the requested associations.

*Dana Sherson*

* Add `touch_all` method to `ActiveRecord::Relation`.

Example:
@@ -98,26 +98,30 @@ def preload(records, associations, preload_scope = nil)
private

# Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope)
def preloaders_on(association, records, scope, polymorphic_parent = false)
case association
when Hash
preloaders_for_hash(association, records, scope)
preloaders_for_hash(association, records, scope, polymorphic_parent)
when Symbol
preloaders_for_one(association, records, scope)
preloaders_for_one(association, records, scope, polymorphic_parent)
when String
preloaders_for_one(association.to_sym, records, scope)
preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
else
raise ArgumentError, "#{association.inspect} was not recognized for preload"
end
end

def preloaders_for_hash(association, records, scope)
def preloaders_for_hash(association, records, scope, polymorphic_parent)
association.flat_map { |parent, child|
loaders = preloaders_for_one parent, records, scope
loaders = preloaders_for_one parent, records, scope, polymorphic_parent

recs = loaders.flat_map(&:preloaded_records).uniq

reflection = records.first.class._reflect_on_association(parent)
polymorphic_parent = reflection && reflection.options[:polymorphic]

loaders.concat Array.wrap(child).flat_map { |assoc|
preloaders_on assoc, recs, scope
preloaders_on assoc, recs, scope, polymorphic_parent
}
loaders
}
@@ -135,8 +139,8 @@ def preloaders_for_hash(association, records, scope)
# Additionally, polymorphic belongs_to associations can have multiple associated
# classes, depending on the polymorphic_type field. So we group by the classes as
# well.
def preloaders_for_one(association, records, scope)
grouped_records(association, records).flat_map do |reflection, klasses|
def preloaders_for_one(association, records, scope, polymorphic_parent)
grouped_records(association, records, polymorphic_parent).flat_map do |reflection, klasses|
klasses.map do |rhs_klass, rs|
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
loader.run self
@@ -145,10 +149,11 @@ def preloaders_for_one(association, records, scope)
end
end

def grouped_records(association, records)
def grouped_records(association, records, polymorphic_parent)
h = {}
records.each do |record|
next unless record
next if polymorphic_parent && !record.class._reflect_on_association(association)
assoc = record.association(association)
next unless assoc.klass
klasses = h[assoc.reflection] ||= {}
@@ -1515,6 +1515,35 @@ def test_preloading_has_many_through_with_custom_scope
Author.preload(:readonly_comments).first!
end

test "preloading through a polymorphic association doesn't require the association to exist" do
sponsors = []
assert_queries 5 do
sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [:post, :membership]).to_a
end
# check the preload worked
assert_queries 0 do
sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
end
end

test "preloading a regular association through a polymorphic association doesn't require the association to exist on all types" do
sponsors = []
assert_queries 6 do
sponsors = Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :first_comment }, :membership]).to_a
end
# check the preload worked
assert_queries 0 do
sponsors.map(&:sponsorable).map { |s| s.respond_to?(:posts) ? s.post.author : s.membership }
end
end

test "preloading a regular association with a typo through a polymorphic association still raises" do
# this test contains an intentional typo of first -> fist
assert_raises(ActiveRecord::AssociationNotFoundError) do
Sponsor.where(sponsorable_id: 1).preload(sponsorable: [{ post: :fist_comment }, :membership]).to_a
end
end

private
def find_all_ordered(klass, include = nil)
klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
@@ -10,3 +10,6 @@ crazy_club_sponsor_for_groucho:
sponsor_club: crazy_club
sponsorable_id: 3
sponsorable_type: Member
sponsor_for_author_david:
sponsorable_id: 1
sponsorable_type: Author

0 comments on commit 75ef18c

Please sign in to comment.