association.rb 4.1 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
15
          @associated_records_by_owner = nil
J
Jon Leighton 已提交
16 17
        end

18
        def run(preloader)
19
          preload(preloader)
J
Jon Leighton 已提交
20 21
        end

22
        def preload(preloader)
J
Jon Leighton 已提交
23 24 25
          raise NotImplementedError
        end

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

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

        def query_scope(ids)
J
Jon Leighton 已提交
35
          scope.where(association_key.in(ids))
J
Jon Leighton 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
        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|
60
            owner[owner_key_name]
J
Jon Leighton 已提交
61 62 63 64 65 66 67
          end
        end

        def options
          reflection.options
        end

68
        def preloaded_records
69
          @associated_records_by_owner.values.flatten
70 71
        end

A
Aaron Patterson 已提交
72 73
        private

74
        def associated_records_by_owner(preloader)
75 76
          return @associated_records_by_owner if @associated_records_by_owner

77 78
          owners_map = owners_by_key
          owner_keys = owners_map.keys.compact
J
Jon Leighton 已提交
79

80
          # Each record may have multiple owners, and vice-versa
81 82 83
          records_by_owner = Hash.new do |h,owner|
            h[owner] = []
          end
84

85
          if owner_keys.any?
J
Jon Leighton 已提交
86 87
            # 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
88
            sliced  = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
89

90 91
            records = load_slices sliced
            records.each do |record, owner_key|
92 93
              owners_map[owner_key].each do |owner|
                records_by_owner[owner] << record
94
              end
95
            end
J
Jon Leighton 已提交
96
          end
97 98 99
          owners.each_with_object(records_by_owner) do |owner,h|
            h[owner] ||= []
          end
100

101
          @associated_records_by_owner = records_by_owner
J
Jon Leighton 已提交
102 103
        end

104 105 106 107 108 109
        def load_slices(slices)
          slices.flat_map { |slice|
            records_for(slice).to_a.map! { |record|
              [record, record[association_key_name]]
            }
          }
110 111
        end

112
        def reflection_scope
113
          @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
114 115
        end

J
Jon Leighton 已提交
116
        def build_scope
117
          scope = klass.unscoped
J
Jon Leighton 已提交
118

119 120
          values         = reflection_scope.values
          preload_values = preload_scope.values
J
Jon Leighton 已提交
121

122 123 124 125 126
          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 已提交
127

128 129 130 131 132 133 134 135
          if preload_values.key? :order
            scope.order! preload_values[:order]
          else
            if values.key? :order
              scope.order! values[:order]
            end
          end

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

140
          klass.default_scoped.merge(scope)
J
Jon Leighton 已提交
141 142 143 144 145
        end
      end
    end
  end
end