query_methods.rb 7.5 KB
Newer Older
1
require 'active_support/core_ext/array/wrap'
2 3
require 'active_support/core_ext/object/blank'

4 5
module ActiveRecord
  module QueryMethods
6 7
    extend ActiveSupport::Concern

8
    attr_accessor :includes_values, :eager_load_values, :preload_values,
A
Aaron Patterson 已提交
9 10
                  :select_values, :group_values, :order_values, :joins_values,
                  :where_values, :having_values, :bind_values,
11
                  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
12

13
    def includes(*args)
14 15
      args.reject! {|a| a.blank? }

16
      return self if args.empty?
A
Aaron Patterson 已提交
17

18
      relation = clone
A
Aaron Patterson 已提交
19
      relation.includes_values = (relation.includes_values + args).flatten.uniq
20
      relation
21
    end
22

23
    def eager_load(*args)
24 25
      return self if args.blank?

26
      relation = clone
27
      relation.eager_load_values += args
28
      relation
29 30 31
    end

    def preload(*args)
32 33
      return self if args.blank?

34
      relation = clone
35
      relation.preload_values += args
36
      relation
37
    end
38

39
    def select(value = Proc.new)
40
      if block_given?
41
        to_a.select {|*block_args| value.call(*block_args) }
42
      else
43
        relation = clone
44
        relation.select_values += Array.wrap(value)
45
        relation
S
Santiago Pastorino 已提交
46
      end
47
    end
S
Santiago Pastorino 已提交
48

49
    def group(*args)
50 51
      return self if args.blank?

52
      relation = clone
53
      relation.group_values += args.flatten
54
      relation
55
    end
56

57
    def order(*args)
58 59
      return self if args.blank?

60
      relation = clone
61
      relation.order_values += args.flatten
62
      relation
63
    end
64

65
    def joins(*args)
66
      return self if args.compact.blank?
67

68 69
      relation = clone

A
Aaron Patterson 已提交
70
      args.flatten!
71
      relation.joins_values += args
72 73

      relation
P
Pratik Naik 已提交
74 75
    end

A
Aaron Patterson 已提交
76 77 78 79 80 81
    def bind(value)
      relation = clone
      relation.bind_values += [value]
      relation
    end

82
    def where(opts, *rest)
83 84
      return self if opts.blank?

85
      relation = clone
86
      relation.where_values += build_where(opts, rest)
87
      relation
88
    end
P
Pratik Naik 已提交
89

90
    def having(*args)
91 92
      return self if args.blank?

93
      relation = clone
94
      relation.having_values += build_where(*args)
95
      relation
96 97
    end

98
    def limit(value)
99 100 101
      relation = clone
      relation.limit_value = value
      relation
102 103
    end

104
    def offset(value)
105 106 107
      relation = clone
      relation.offset_value = value
      relation
108 109 110
    end

    def lock(locks = true)
111 112
      relation = clone

113
      case locks
114
      when String, TrueClass, NilClass
115
        relation.lock_value = locks || true
116
      else
117
        relation.lock_value = false
118
      end
119 120

      relation
121 122
    end

123
    def readonly(value = true)
124 125 126
      relation = clone
      relation.readonly_value = value
      relation
127 128
    end

129
    def create_with(value)
130
      relation = clone
131
      relation.create_with_value = value && (@create_with_value || {}).merge(value)
132
      relation
133 134
    end

135
    def from(value)
136 137 138
      relation = clone
      relation.from_value = value
      relation
139 140
    end

141 142
    def extending(*modules)
      modules << Module.new(&Proc.new) if block_given?
143

144 145
      return self if modules.empty?

146
      relation = clone
147
      relation.send(:apply_modules, modules.flatten)
148
      relation
149 150
    end

151
    def reverse_order
N
Neeraj Singh 已提交
152
      order_clause = arel.order_clauses
153

N
Neeraj Singh 已提交
154
      order = order_clause.empty? ?
155
        "#{table_name}.#{primary_key} DESC" :
N
Neeraj Singh 已提交
156
        reverse_sql_order(order_clause).join(', ')
157

158
      except(:order).order(Arel.sql(order))
159 160
    end

161 162
    def arel
      @arel ||= build_arel
163 164
    end

165
    def build_arel
166
      arel = table.from table
167

A
Aaron Patterson 已提交
168
      build_joins(arel, @joins_values) unless @joins_values.empty?
169

A
Aaron Patterson 已提交
170
      collapse_wheres(arel, (@where_values - ['']).uniq)
171

A
Aaron Patterson 已提交
172
      arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
173

174
      arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
A
Aaron Patterson 已提交
175
      arel.skip(@offset_value) if @offset_value
A
Aaron Patterson 已提交
176

A
Aaron Patterson 已提交
177
      arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
178

A
Aaron Patterson 已提交
179
      arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty?
180

A
Aaron Patterson 已提交
181
      build_select(arel, @select_values.uniq)
182

A
Aaron Patterson 已提交
183 184
      arel.from(@from_value) if @from_value
      arel.lock(@lock_value) if @lock_value
185 186

      arel
187 188
    end

189 190
    private

191
    def custom_join_ast(table, joins)
192 193
      joins = joins.reject { |join| join.blank? }

194
      return [] if joins.empty?
195 196 197

      @implicit_readonly = true

198
      joins.map do |join|
199 200 201 202 203 204
        case join
        when Array
          join = Arel.sql(join.join(' ')) if array_of_strings?(join)
        when String
          join = Arel.sql(join)
        end
205
        table.create_string_join(join)
206
      end
207 208
    end

209 210 211 212
    def collapse_wheres(arel, wheres)
      equalities = wheres.grep(Arel::Nodes::Equality)

      groups = equalities.group_by do |equality|
213
        equality.left
214 215 216
      end

      groups.each do |_, eqls|
217 218
        test = eqls.inject(eqls.shift) do |memo, expr|
          memo.or(expr)
219
        end
220
        arel.where(test)
221 222 223 224
      end

      (wheres - equalities).each do |where|
        where = Arel.sql(where) if String === where
225
        arel.where(Arel::Nodes::Grouping.new(where))
226 227 228
      end
    end

229
    def build_where(opts, other = [])
A
Aaron Patterson 已提交
230 231
      case opts
      when String, Array
232
        [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
A
Aaron Patterson 已提交
233 234
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
235
        PredicateBuilder.build_from_hash(table.engine, attributes, table)
236
      else
237
        [opts]
238 239 240
      end
    end

241
    def build_joins(manager, joins)
A
Aaron Patterson 已提交
242 243 244 245 246 247 248 249
      buckets = joins.group_by do |join|
        case join
        when String
          'string_join'
        when Hash, Symbol, Array
          'association_join'
        when ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
          'stashed_join'
250 251
        when Arel::Nodes::Join
          'join_node'
A
Aaron Patterson 已提交
252 253 254
        else
          raise 'unknown class: %s' % join.class.name
        end
255 256
      end

A
Aaron Patterson 已提交
257 258
      association_joins         = buckets['association_join'] || []
      stashed_association_joins = buckets['stashed_join'] || []
259
      join_nodes                = buckets['join_node'] || []
A
Aaron Patterson 已提交
260 261 262
      string_joins              = (buckets['string_join'] || []).map { |x|
        x.strip
      }.uniq
263

A
Aaron Patterson 已提交
264
      join_list = custom_join_ast(manager, string_joins)
265

266 267 268 269 270 271 272 273 274
      join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(
        @klass,
        association_joins,
        join_list
      )

      join_nodes.each do |join|
        join_dependency.table_aliases[join.left.name.downcase] = 1
      end
275 276 277 278 279

      join_dependency.graft(*stashed_association_joins)

      @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?

A
Aaron Patterson 已提交
280
      # FIXME: refactor this to build an AST
281
      join_dependency.join_associations.each do |association|
282
        association.join_to(manager)
283 284
      end

285
      manager.join_sources.concat join_nodes.uniq
286
      manager.join_sources.concat join_list
287 288

      manager
289 290
    end

291
    def build_select(arel, selects)
292
      unless selects.empty?
293
        @implicit_readonly = false
294
        arel.project(*selects)
295
      else
296
        arel.project(Arel.sql(@klass.quoted_table_name + '.*'))
297 298 299
      end
    end

P
Pratik Naik 已提交
300
    def apply_modules(modules)
301
      unless modules.empty?
302
        @extensions += modules
303
        modules.each {|extension| extend(extension) }
N
Neeraj Singh 已提交
304
      end
P
Pratik Naik 已提交
305 306
    end

307
    def reverse_sql_order(order_query)
308 309 310
      order_query.join(', ').split(',').collect do |s|
        s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
      end
311 312
    end

P
Pratik Naik 已提交
313 314 315 316
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

317 318
  end
end