association.rb 3.8 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 33 34 35
          query_scope(ids).to_a
        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 61 62 63 64 65 66 67 68 69 70 71 72 73 74
        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
75 76
          owners_map = owners_by_key
          owner_keys = owners_map.keys.compact
J
Jon Leighton 已提交
77 78 79 80 81 82

          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
83
            sliced  = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
A
Aaron Patterson 已提交
84
            records = sliced.flat_map { |slice| records_for(slice) }
J
Jon Leighton 已提交
85 86 87 88 89
          end

          # Each record may have multiple owners, and vice-versa
          records_by_owner = Hash[owners.map { |owner| [owner, []] }]
          records.each do |record|
90
            owner_key = owner_id_for records, record
J
Jon Leighton 已提交
91

92
            owners_map[owner_key].each do |owner|
J
Jon Leighton 已提交
93 94 95 96 97 98
              records_by_owner[owner] << record
            end
          end
          records_by_owner
        end

99 100 101 102
        def owner_id_for(results, record)
          record[association_key_name].to_s
        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