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
        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)
J
Jon Leighton 已提交
32
          scope.where(association_key.in(ids))
J
Jon Leighton 已提交
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 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

          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
79
            sliced  = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
N
Neeraj Singh 已提交
80
            records = sliced.flat_map { |slice| records_for(slice).to_a }
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
          scope = klass.unscoped
J
Jon Leighton 已提交
101

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

105 106 107 108 109
          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 已提交
110 111

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

115
          klass.default_scoped.merge(scope)
J
Jon Leighton 已提交
116 117 118 119 120
        end
      end
    end
  end
end