diff options
Diffstat (limited to 'activerecord/lib/active_record/association_preload.rb')
-rw-r--r-- | activerecord/lib/active_record/association_preload.rb | 108 |
1 files changed, 57 insertions, 51 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 6d905fe6fe..ecf7b6c210 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -127,8 +127,7 @@ module ActiveRecord association_proxy = parent_record.send(reflection_name) association_proxy.loaded association_proxy.target.push(*Array.wrap(associated_record)) - - association_proxy.__send__(:set_inverse_instance, associated_record, parent_record) + association_proxy.send(:set_inverse_instance, associated_record) end end @@ -148,16 +147,19 @@ module ActiveRecord def set_association_single_records(id_to_record_map, reflection_name, associated_records, key) seen_keys = {} associated_records.each do |associated_record| + seen_key = associated_record[key].to_s + #this is a has_one or belongs_to: there should only be one record. #Unfortunately we can't (in portable way) ask the database for #'all records where foo_id in (x,y,z), but please # only one row per distinct foo_id' so this where we enforce that - next if seen_keys[associated_record[key].to_s] - seen_keys[associated_record[key].to_s] = true - mapped_records = id_to_record_map[associated_record[key].to_s] + next if seen_keys.key? seen_key + + 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.__send__(:set_inverse_instance, associated_record, mapped_record) + association_proxy.send(:set_inverse_instance, associated_record) end end @@ -188,19 +190,17 @@ module ActiveRecord left = reflection.klass.arel_table - table_name = reflection.klass.quoted_table_name id_to_record_map, ids = construct_id_map(records) records.each {|record| record.send(reflection.name).loaded} options = reflection.options - conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}" - conditions << append_conditions(reflection, preload_options) - right = Arel::Table.new(options[:join_table]).alias('t0') - condition = left[reflection.klass.primary_key].eq( + + + join_condition = left[reflection.klass.primary_key].eq( right[reflection.association_foreign_key]) - join = left.create_join(right, left.create_on(condition)) + join = left.create_join(right, left.create_on(join_condition)) select = [ # FIXME: options[:select] is always nil in the tests. Do we really # need it? @@ -216,8 +216,16 @@ module ActiveRecord associated_records_proxy.joins_values = [join] associated_records_proxy.select_values = select + custom_conditions = append_conditions(reflection, preload_options) + all_associated_records = associated_records(ids) do |some_ids| - associated_records_proxy.where([conditions, some_ids]).to_a + 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 set_association_collection_records(id_to_record_map, reflection.name, all_associated_records, 'the_parent_record_id') @@ -324,61 +332,54 @@ module ActiveRecord if klass = record.send(polymorph_type) klass_id = record.send(primary_key_name) if klass_id - id_map = klasses_and_ids[klass] ||= {} + id_map = klasses_and_ids[klass.constantize] ||= {} (id_map[klass_id.to_s] ||= []) << record end end end else - id_map = {} - records.each do |record| + id_map = records.group_by do |record| key = record.send(primary_key_name) - (id_map[key.to_s] ||= []) << record if key + key && key.to_s end - klasses_and_ids[reflection.klass.name] = id_map unless id_map.empty? + id_map.delete nil + klasses_and_ids[reflection.klass] = id_map unless id_map.empty? end - klasses_and_ids.each do |klass_name, _id_map| - klass = klass_name.constantize - - table_name = klass.quoted_table_name + klasses_and_ids.each do |klass, _id_map| + table = klass.arel_table primary_key = (reflection.options[:primary_key] || klass.primary_key).to_s - column_type = klass.columns.detect{|c| c.name == primary_key}.type - - ids = _id_map.keys.map do |id| - if column_type == :integer - id.to_i - elsif column_type == :float - id.to_f - else - id - end - end + method = in_or_equal(_id_map.keys) + conditions = table[primary_key].send(*method) - conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}" - conditions << append_conditions(reflection, preload_options) + 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, ids]).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a + associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a set_association_single_records(_id_map, reflection.name, associated_records, primary_key) end end def find_associated_records(ids, reflection, preload_options) - options = reflection.options - table_name = reflection.klass.quoted_table_name + options = reflection.options + table = reflection.klass.arel_table + + conditions = [] + + key = reflection.primary_key_name if interface = reflection.options[:as] - conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'" - else - foreign_key = reflection.primary_key_name - conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}" + key = "#{interface}_id" + conditions << table["#{interface}_type"].eq(base_class.sti_name) end - conditions << append_conditions(reflection, preload_options) + conditions += append_conditions(reflection, preload_options) find_options = { - :select => preload_options[:select] || options[:select] || Arel::SqlLiteral.new("#{table_name}.*"), + :select => preload_options[:select] || options[:select] || table[Arel.star], :include => preload_options[:include] || options[:include], :joins => options[:joins], :group => preload_options[:group] || options[:group], @@ -386,19 +387,24 @@ module ActiveRecord } associated_records(ids) do |some_ids| - reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => [conditions, some_ids])).to_a + method = in_or_equal(some_ids) + where = conditions.inject(table[key].send(*method)) do |ast, cond| + ast.and cond + end + + reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => where)).to_a end end def append_conditions(reflection, preload_options) - sql = "" - sql << " AND (#{reflection.sanitized_conditions})" if reflection.sanitized_conditions - sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions] - sql + [ + ("(#{reflection.sanitized_conditions})" if reflection.sanitized_conditions), + ("(#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]), + ].compact.map { |x| Arel.sql x } end - def in_or_equals_for_ids(ids) - ids.size > 1 ? "IN (?)" : "= ?" + def in_or_equal(ids) + ids.length == 1 ? ['eq', ids.first] : ['in', ids] end # Some databases impose a limit on the number of ids in a list (in Oracle its 1000) |