diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/preloader')
3 files changed, 39 insertions, 27 deletions
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 0cc836f991..928da71eed 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -29,6 +29,10 @@ module ActiveRecord end def records_for(ids) + query_scope(ids) + end + + def query_scope(ids) scope.where(association_key.in(ids)) end @@ -52,12 +56,9 @@ module ActiveRecord raise NotImplementedError end - # We're converting to a string here because postgres will return the aliased association - # key in a habtm as a string (for whatever reason) def owners_by_key @owners_by_key ||= owners.group_by do |owner| - key = owner[owner_key_name] - key && key.to_s + owner[owner_key_name] end end @@ -71,27 +72,34 @@ module ActiveRecord owners_map = owners_by_key owner_keys = owners_map.keys.compact - if klass.nil? || owner_keys.empty? - records = [] - else + # Each record may have multiple owners, and vice-versa + records_by_owner = Hash[owners.map { |owner| [owner, []] }] + + if klass && owner_keys.any? # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - records = sliced.flat_map { |slice| records_for(slice).to_a } + sliced.each { |slice| + records = records_for(slice) + caster = type_caster(records, association_key_name) + records.each do |record| + owner_key = caster.call record[association_key_name] + + owners_map[owner_key].each do |owner| + records_by_owner[owner] << record + end + end + } end - # Each record may have multiple owners, and vice-versa - records_by_owner = Hash[owners.map { |owner| [owner, []] }] - records.each do |record| - owner_key = record[association_key_name].to_s - - owners_map[owner_key].each do |owner| - records_by_owner[owner] << record - end - end records_by_owner end + IDENTITY = lambda { |value| value } + def type_caster(results, name) + IDENTITY + end + def reflection_scope @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped end diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb index 9a3fada380..c042a44b21 100644 --- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb @@ -12,7 +12,7 @@ module ActiveRecord # Unlike the other associations, we want to get a raw array of rows so that we can # access the aliased column on the join table def records_for(ids) - scope = super + scope = query_scope ids klass.connection.select_all(scope.arel, 'SQL', scope.bind_values) end @@ -40,6 +40,11 @@ module ActiveRecord end end + def type_caster(results, name) + caster = results.column_types.fetch(name, results.identity_type) + lambda { |value| caster.type_cast value } + end + def build_scope super.joins(join).select(join_select) end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index de06931845..2c625cec04 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -27,17 +27,16 @@ module ActiveRecord def through_records_by_owner Preloader.new(owners, through_reflection.name, through_scope).run - Hash[owners.map do |owner| - through_records = Array.wrap(owner.send(through_reflection.name)) + should_reset = (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) - # Dont cache the association - we would only be caching a subset - if (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) - owner.association(through_reflection.name).reset - end + owners.each_with_object({}) do |owner, h| + association = owner.association through_reflection.name + h[owner] = Array(association.reader) - [owner, through_records] - end] + # Dont cache the association - we would only be caching a subset + association.reset if should_reset + end end def through_scope |