association.rb 3.6 KB
Newer Older
J
Jon Leighton 已提交
1 2 3 4
module ActiveRecord
  module Associations
    class Preloader
      class Association #:nodoc:
5 6 7 8 9 10 11 12 13 14
        attr_reader :owners, :reflection, :preload_scope, :model, :klass

        def initialize(klass, owners, reflection, preload_scope)
          @klass         = klass
          @owners        = owners
          @reflection    = reflection
          @preload_scope = preload_scope
          @model         = owners.first && owners.first.class
          @scoped        = nil
          @owners_by_key = nil
J
Jon Leighton 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
        end

        def run
          unless owners.first.association(reflection.name).loaded?
            preload
          end
        end

        def preload
          raise NotImplementedError
        end

        def scoped
          @scoped ||= build_scope
        end

        def records_for(ids)
          scoped.where(association_key.in(ids))
        end

        def table
          klass.arel_table
        end

        # The name of the key on the associated records
        def association_key_name
          raise NotImplementedError
        end

        # This is overridden by HABTM as the condition should be on the foreign_key column in
        # the join table
        def association_key
          table[association_key_name]
        end

        # The name of the key on the model which declares the association
        def owner_key_name
          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
          end
        end

        def options
          reflection.options
        end

        private

        def associated_records_by_owner
71 72
          owners_map = owners_by_key
          owner_keys = owners_map.keys.compact
J
Jon Leighton 已提交
73 74 75 76 77 78 79

          if klass.nil? || owner_keys.empty?
            records = []
          else
            # 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(model.connection.in_clause_length || owner_keys.size)
80
            records = sliced.map { |slice| records_for(slice).to_a }.flatten
J
Jon Leighton 已提交
81 82 83 84 85 86 87
          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

88
            owners_map[owner_key].each do |owner|
J
Jon Leighton 已提交
89 90 91 92 93 94
              records_by_owner[owner] << record
            end
          end
          records_by_owner
        end

95
        def reflection_scope
96
          @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
97 98
        end

J
Jon Leighton 已提交
99
        def build_scope
100 101
          scope = klass.unscoped
          scope.default_scoped = true
J
Jon Leighton 已提交
102

103 104
          values         = reflection_scope.values
          preload_values = preload_scope.values
J
Jon Leighton 已提交
105

106 107 108 109 110
          scope.where_values      = Array(values[:where])      + Array(preload_values[:where])
          scope.references_values = Array(values[:references]) + Array(preload_values[:references])

          scope.select!   preload_values[:select] || values[:select] || table[Arel.star]
          scope.includes! preload_values[:includes] || values[:includes]
J
Jon Leighton 已提交
111 112

          if options[:as]
113
            scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
J
Jon Leighton 已提交
114 115 116 117 118 119 120 121
          end

          scope
        end
      end
    end
  end
end