relation.rb 5.3 KB
Newer Older
1 2
module ActiveRecord
  class Relation
3
    include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods
4

5
    delegate :length, :collect, :map, :each, :all?, :to => :to_a
6

7 8
    attr_reader :relation, :klass
    attr_writer :readonly, :table
9
    attr_accessor :preload_associations, :eager_load_associations, :includes_associations, :create_with_attributes
10

11
    def initialize(klass, relation)
E
Emilio Tagua 已提交
12
      @klass, @relation = klass, relation
13 14
      @preload_associations = []
      @eager_load_associations = []
15
      @includes_associations = []
16
      @loaded, @readonly = false
17 18
    end

P
Pratik Naik 已提交
19
    def new(*args, &block)
20 21 22 23 24 25 26 27 28
      with_create_scope { @klass.new(*args, &block) }
    end

    def create(*args, &block)
      with_create_scope { @klass.create(*args, &block) }
    end

    def create!(*args, &block)
      with_create_scope { @klass.create!(*args, &block) }
P
Pratik Naik 已提交
29 30
    end

31 32
    def respond_to?(method, include_private = false)
      return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
33 34 35 36 37 38 39 40

      if match = DynamicFinderMatch.match(method)
        return true if @klass.send(:all_attributes_exists?, match.attribute_names)
      elsif match = DynamicScopeMatch.match(method)
        return true if @klass.send(:all_attributes_exists?, match.attribute_names)
      else
        super
      end
41 42
    end

P
Pratik Naik 已提交
43 44 45
    def to_a
      return @records if loaded?

46
      find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
47 48

      @records = if find_with_associations
49 50
        begin
          @klass.send(:find_with_associations, {
P
Pratik Naik 已提交
51 52 53
            :select => @relation.send(:select_clauses).join(', '),
            :joins => @relation.joins(relation),
            :group => @relation.send(:group_clauses).join(', '),
54
            :order => order_clause,
P
Pratik Naik 已提交
55
            :conditions => where_clause,
P
Pratik Naik 已提交
56
            :limit => @relation.taken,
57
            :offset => @relation.skipped,
J
Jeremy Kemper 已提交
58
            :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
P
Pratik Naik 已提交
59
            },
60
            ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil))
61 62
        rescue ThrowResult
          []
P
Pratik Naik 已提交
63 64 65 66 67
        end
      else
        @klass.find_by_sql(@relation.to_sql)
      end

68
      preload = @preload_associations
69
      preload +=  @includes_associations unless find_with_associations
70 71
      preload.each {|associations| @klass.send(:preload_associations, @records, associations) } 

P
Pratik Naik 已提交
72 73 74 75 76 77 78 79
      @records.each { |record| record.readonly! } if @readonly

      @loaded = true
      @records
    end

    alias all to_a

80 81 82 83 84 85 86 87
    def size
      loaded? ? @records.length : count
    end

    def empty?
      loaded? ? @records.empty? : count.zero?
    end

P
Pratik Naik 已提交
88 89 90 91 92 93 94 95 96 97 98 99
    def any?
      if block_given?
        to_a.any? { |*block_args| yield(*block_args) }
      else
        !empty?
      end
    end

    def many?
      if block_given?
        to_a.many? { |*block_args| yield(*block_args) }
      else
100
        @relation.send(:taken).present? ? to_a.many? : size > 1
P
Pratik Naik 已提交
101 102 103
      end
    end

P
Pratik Naik 已提交
104 105 106 107 108
    def destroy_all
      to_a.each {|object| object.destroy}
      reset
    end

P
Pratik Naik 已提交
109
    def delete_all
110
      @relation.delete.tap { reset }
P
Pratik Naik 已提交
111 112
    end

113 114 115 116
    def delete(id_or_array)
      where(@klass.primary_key => id_or_array).delete_all
    end

P
Pratik Naik 已提交
117 118 119 120
    def loaded?
      @loaded
    end

121 122
    def reload
      @loaded = false
P
Pratik Naik 已提交
123 124 125 126
      reset
    end

    def reset
127
      @first = @last = @to_sql = @order_clause = @scope_for_create = nil
P
Pratik Naik 已提交
128
      @records = []
129 130 131
      self
    end

132
    def table
133
      @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine)
134 135 136 137 138 139
    end

    def primary_key
      @primary_key ||= table[@klass.primary_key]
    end

P
Pratik Naik 已提交
140 141 142 143
    def to_sql
      @to_sql ||= @relation.to_sql
    end

144
    protected
145 146 147 148 149 150

    def method_missing(method, *args, &block)
      if @relation.respond_to?(method)
        @relation.send(method, *args, &block)
      elsif Array.method_defined?(method)
        to_a.send(method, *args, &block)
151 152 153 154 155
      elsif match = DynamicFinderMatch.match(method)
        attributes = match.attribute_names
        super unless @klass.send(:all_attributes_exists?, attributes)

        if match.finder?
156
          find_by_attributes(match, attributes, *args)
157 158
        elsif match.instantiator?
          find_or_instantiator_by_attributes(match, attributes, *args, &block)
159
        end
160 161
      else
        super
162
      end
163 164
    end

165
    def with_create_scope
166
      @klass.send(:with_scope, :create => scope_for_create) { yield }
167 168
    end

169 170 171 172 173 174
    def scope_for_create
      @scope_for_create ||= begin
        @create_with_attributes || wheres.inject({}) do |hash, where|
          hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
          hash
        end
P
Pratik Naik 已提交
175 176 177
      end
    end

178
    def where_clause(join_string = " AND ")
P
Pratik Naik 已提交
179 180
      @relation.send(:where_clauses).join(join_string)
    end
181

182 183 184 185
    def order_clause
      @order_clause ||= @relation.send(:order_clauses).join(', ')
    end

186
    def references_eager_loaded_tables?
187 188
      joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
      (tables_in_string(to_sql) - joined_tables).any?
189 190 191 192 193 194 195
    end

    def tables_in_string(string)
      return [] if string.blank?
      string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
    end

196 197
  end
end