association.rb 3.7 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
        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
J
Jon Leighton 已提交
13
          @scope         = nil
14
          @owners_by_key = nil
J
Jon Leighton 已提交
15 16 17 18 19 20 21 22 23 24 25 26
        end

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

        def preload
          raise NotImplementedError
        end

J
Jon Leighton 已提交
27 28
        def scope
          @scope ||= build_scope
J
Jon Leighton 已提交
29 30 31
        end

        def records_for(ids)
A
Aaron Patterson 已提交
32
          query_scope(ids)
A
Aaron Patterson 已提交
33 34 35
        end

        def query_scope(ids)
J
Jon Leighton 已提交
36
          scope.where(association_key.in(ids))
J
Jon Leighton 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
        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

        def owners_by_key
          @owners_by_key ||= owners.group_by do |owner|
61
            owner[owner_key_name]
J
Jon Leighton 已提交
62 63 64 65 66 67 68 69 70 71
          end
        end

        def options
          reflection.options
        end

        private

        def associated_records_by_owner
72 73
          owners_map = owners_by_key
          owner_keys = owners_map.keys.compact
J
Jon Leighton 已提交
74

75 76 77 78
          # Each record may have multiple owners, and vice-versa
          records_by_owner = Hash[owners.map { |owner| [owner, []] }]

          if klass && owner_keys.any?
J
Jon Leighton 已提交
79 80
            # 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
81
            sliced  = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
A
Aaron Patterson 已提交
82 83
            sliced.each { |slice|
              records = records_for(slice)
84
              caster = type_caster(records, association_key_name)
A
Aaron Patterson 已提交
85
              records.each do |record|
86
                owner_key = caster.call record[association_key_name]
A
Aaron Patterson 已提交
87 88 89 90

                owners_map[owner_key].each do |owner|
                  records_by_owner[owner] << record
                end
91
              end
A
Aaron Patterson 已提交
92
            }
J
Jon Leighton 已提交
93
          end
94

J
Jon Leighton 已提交
95 96 97
          records_by_owner
        end

98 99 100
        IDENTITY = lambda { |value| value }
        def type_caster(results, name)
          IDENTITY
101 102
        end

103
        def reflection_scope
104
          @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
105 106
        end

J
Jon Leighton 已提交
107
        def build_scope
108
          scope = klass.unscoped
J
Jon Leighton 已提交
109

110 111
          values         = reflection_scope.values
          preload_values = preload_scope.values
J
Jon Leighton 已提交
112

113 114 115 116 117
          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 已提交
118 119

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

123
          klass.default_scoped.merge(scope)
J
Jon Leighton 已提交
124 125 126 127 128
        end
      end
    end
  end
end