aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/association_preload.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/association_preload.rb')
-rw-r--r--activerecord/lib/active_record/association_preload.rb127
1 files changed, 65 insertions, 62 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index ecf7b6c210..cba4bab3ef 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -126,21 +126,21 @@ module ActiveRecord
parent_records.each do |parent_record|
association_proxy = parent_record.send(reflection_name)
association_proxy.loaded
- association_proxy.target.push(*Array.wrap(associated_record))
+ association_proxy.target.concat(Array.wrap(associated_record))
association_proxy.send(:set_inverse_instance, associated_record)
end
end
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
parent_records.each do |parent_record|
- parent_record.send("set_#{reflection_name}_target", associated_record)
+ parent_record.send(:association_proxy, reflection_name).target = associated_record
end
end
- def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
+ def set_association_collection_records(id_to_parent_map, reflection_name, associated_records, key)
associated_records.each do |associated_record|
- mapped_records = id_to_record_map[associated_record[key].to_s]
- add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
+ parent_records = id_to_parent_map[associated_record[key].to_s]
+ add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
end
end
@@ -158,14 +158,15 @@ module ActiveRecord
seen_keys[seen_key] = true
mapped_records = id_to_record_map[seen_key]
mapped_records.each do |mapped_record|
- association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy = mapped_record.send(:association_proxy, reflection_name)
+ association_proxy.target = associated_record
association_proxy.send(:set_inverse_instance, associated_record)
end
end
id_to_record_map.each do |id, records|
- next if seen_keys.include?(id.to_s)
- records.each {|record| record.send("set_#{reflection_name}_target", nil) }
+ next if seen_keys.include?(id)
+ add_preloaded_record_to_collection(records, reflection_name, nil)
end
end
@@ -174,29 +175,23 @@ module ActiveRecord
# <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
# and +ids+ is an Array of record IDs.
def construct_id_map(records, primary_key=nil)
- id_to_record_map = {}
- ids = []
- records.each do |record|
+ records.group_by do |record|
primary_key ||= record.class.primary_key
- ids << record[primary_key]
- mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
- mapped_records << record
+ record[primary_key].to_s
end
- ids.uniq!
- return id_to_record_map, ids
end
def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
left = reflection.klass.arel_table
- id_to_record_map, ids = construct_id_map(records)
+ id_to_record_map = construct_id_map(records)
+
records.each {|record| record.send(reflection.name).loaded}
options = reflection.options
right = Arel::Table.new(options[:join_table]).alias('t0')
-
join_condition = left[reflection.klass.primary_key].eq(
right[reflection.association_foreign_key])
@@ -205,7 +200,7 @@ module ActiveRecord
# FIXME: options[:select] is always nil in the tests. Do we really
# need it?
options[:select] || left[Arel.star],
- right[reflection.primary_key_name].as(
+ right[reflection.foreign_key].as(
Arel.sql('the_parent_record_id'))
]
@@ -218,34 +213,42 @@ module ActiveRecord
custom_conditions = append_conditions(reflection, preload_options)
- all_associated_records = associated_records(ids) do |some_ids|
- method = in_or_equal(some_ids)
- conditions = right[reflection.primary_key_name].send(*method)
- conditions = custom_conditions.inject(conditions) do |ast, cond|
- ast.and cond
- end
-
- associated_records_proxy.where(conditions).to_a
- end
+ klass = associated_records_proxy.klass
- set_association_collection_records(id_to_record_map, reflection.name, all_associated_records, 'the_parent_record_id')
+ associated_records(id_to_record_map.keys) { |some_ids|
+ method = in_or_equal(some_ids)
+ conditions = right.create_and(
+ [right[reflection.foreign_key].send(*method)] +
+ custom_conditions)
+
+ relation = associated_records_proxy.where(conditions)
+ klass.connection.select_all(relation.arel.to_sql, 'SQL', relation.bind_values)
+ }.map! { |row|
+ parent_records = id_to_record_map[row['the_parent_record_id'].to_s]
+ associated_record = klass.instantiate row
+ add_preloaded_records_to_collection(
+ parent_records, reflection.name, associated_record)
+ associated_record
+ }
end
def preload_has_one_association(records, reflection, preload_options={})
- return if records.first.send("loaded_#{reflection.name}?")
- id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
+ return if records.first.send(:association_proxy, reflection.name).loaded?
+ id_to_record_map = construct_id_map(records, reflection.options[:primary_key])
options = reflection.options
- records.each {|record| record.send("set_#{reflection.name}_target", nil)}
+
+ add_preloaded_record_to_collection(records, reflection.name, nil)
+
if options[:through]
through_records = preload_through_records(records, reflection, options[:through])
unless through_records.empty?
through_reflection = reflections[options[:through]]
- through_primary_key = through_reflection.primary_key_name
+ through_primary_key = through_reflection.foreign_key
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
if through_reflection.macro == :belongs_to
- id_to_record_map = construct_id_map(records, through_primary_key).first
+ id_to_record_map = construct_id_map(records, through_primary_key)
through_primary_key = through_reflection.klass.primary_key
end
@@ -255,7 +258,7 @@ module ActiveRecord
end
end
else
- set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options), reflection.foreign_key)
end
end
@@ -263,8 +266,8 @@ module ActiveRecord
return if records.first.send(reflection.name).loaded?
options = reflection.options
- primary_key_name = reflection.through_reflection_primary_key_name
- id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
+ foreign_key = reflection.through_reflection_foreign_key
+ id_to_record_map = construct_id_map(records, foreign_key || reflection.options[:primary_key])
records.each {|record| record.send(reflection.name).loaded}
if options[:through]
@@ -280,14 +283,14 @@ module ActiveRecord
end
else
- set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
- reflection.primary_key_name)
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options),
+ reflection.foreign_key)
end
end
def preload_through_records(records, reflection, through_association)
if reflection.options[:source_type]
- interface = reflection.source_reflection.options[:foreign_type]
+ interface = reflection.source_reflection.foreign_type
preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
records.compact!
@@ -317,20 +320,17 @@ module ActiveRecord
end
def preload_belongs_to_association(records, reflection, preload_options={})
- return if records.first.send("loaded_#{reflection.name}?")
+ return if records.first.send(:association_proxy, reflection.name).loaded?
options = reflection.options
- primary_key_name = reflection.primary_key_name
klasses_and_ids = {}
if options[:polymorphic]
- polymorph_type = options[:foreign_type]
-
# Construct a mapping from klass to a list of ids to load and a mapping of those ids back
# to their parent_records
records.each do |record|
- if klass = record.send(polymorph_type)
- klass_id = record.send(primary_key_name)
+ if klass = record.send(reflection.foreign_type)
+ klass_id = record.send(reflection.foreign_key)
if klass_id
id_map = klasses_and_ids[klass.constantize] ||= {}
(id_map[klass_id.to_s] ||= []) << record
@@ -339,25 +339,30 @@ module ActiveRecord
end
else
id_map = records.group_by do |record|
- key = record.send(primary_key_name)
+ key = record.send(reflection.foreign_key)
key && key.to_s
end
- id_map.delete nil
klasses_and_ids[reflection.klass] = id_map unless id_map.empty?
end
klasses_and_ids.each do |klass, _id_map|
- table = klass.arel_table
primary_key = (reflection.options[:primary_key] || klass.primary_key).to_s
- method = in_or_equal(_id_map.keys)
- conditions = table[primary_key].send(*method)
+ keys = _id_map.keys.compact
- custom_conditions = append_conditions(reflection, preload_options)
- conditions = custom_conditions.inject(conditions) do |ast, cond|
- ast.and cond
- end
+ unless keys.empty?
+ table = klass.arel_table
+ method = in_or_equal(keys)
+ conditions = table[primary_key].send(*method)
- associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
+ custom_conditions = append_conditions(reflection, preload_options)
+ conditions = custom_conditions.inject(conditions) do |ast, cond|
+ ast.and cond
+ end
+
+ associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
+ else
+ associated_records = []
+ end
set_association_single_records(_id_map, reflection.name, associated_records, primary_key)
end
@@ -369,14 +374,14 @@ module ActiveRecord
conditions = []
- key = reflection.primary_key_name
+ key = reflection.foreign_key
if interface = reflection.options[:as]
key = "#{interface}_id"
conditions << table["#{interface}_type"].eq(base_class.sti_name)
end
- conditions += append_conditions(reflection, preload_options)
+ conditions.concat append_conditions(reflection, preload_options)
find_options = {
:select => preload_options[:select] || options[:select] || table[Arel.star],
@@ -388,9 +393,7 @@ module ActiveRecord
associated_records(ids) do |some_ids|
method = in_or_equal(some_ids)
- where = conditions.inject(table[key].send(*method)) do |ast, cond|
- ast.and cond
- end
+ where = table.create_and(conditions + [table[key].send(*method)])
reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => where)).to_a
end
@@ -413,7 +416,7 @@ module ActiveRecord
in_clause_length = connection.in_clause_length || ids.size
records = []
ids.each_slice(in_clause_length) do |some_ids|
- records += yield(some_ids)
+ records.concat yield(some_ids)
end
records
end