Skip to content
This repository
tree: b57fa0bf69
Fetching contributors…

Cannot retrieve contributors at this time

file 127 lines (104 sloc) 3.759 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
module ActiveRecord
  module Associations
    class Preloader
      class Association #:nodoc:
        attr_reader :owners, :reflection, :preload_options, :model, :klass

        def initialize(klass, owners, reflection, preload_options)
          @klass = klass
          @owners = owners
          @reflection = reflection
          @preload_options = preload_options || {}
          @model = owners.first && owners.first.class
          @scoped = nil
          @owners_by_key = nil
        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
          owners_map = owners_by_key
          owner_keys = owners_map.keys.compact

          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)
            records = sliced.map { |slice| records_for(slice) }.flatten
          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

            owners_map[owner_key].each do |owner|
              records_by_owner[owner] << record
            end
          end
          records_by_owner
        end

        def build_scope
          scope = klass.scoped

          scope = scope.where(process_conditions(options[:conditions]))
          scope = scope.where(process_conditions(preload_options[:conditions]))

          scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
          scope = scope.includes(preload_options[:include] || options[:include])

          if options[:as]
            scope = scope.where(
              klass.table_name => {
                reflection.type => model.base_class.sti_name
              }
            )
          end

          scope
        end

        def process_conditions(conditions)
          if conditions.respond_to?(:to_proc)
            conditions = klass.send(:instance_eval, &conditions)
          end

          if conditions
            klass.send(:sanitize_sql, conditions)
          end
        end
      end
    end
  end
end
Something went wrong with that request. Please try again.