query_methods.rb 7.8 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,
9
                  :select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
10
                  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
11

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

A
Aaron Patterson 已提交
15 16
      return clone if args.empty?

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

22
    def eager_load(*args)
23
      relation = clone
24
      relation.eager_load_values += args unless args.blank?
25
      relation
26 27 28
    end

    def preload(*args)
29
      relation = clone
30
      relation.preload_values += args unless args.blank?
31
      relation
32
    end
33

34
    def select(value = Proc.new)
35
      if block_given?
36
        to_a.select {|*block_args| value.call(*block_args) }
37
      else
38
        relation = clone
39
        relation.select_values += Array.wrap(value)
40
        relation
S
Santiago Pastorino 已提交
41
      end
42
    end
S
Santiago Pastorino 已提交
43

44
    def group(*args)
45
      relation = clone
46
      relation.group_values += args.flatten unless args.blank?
47
      relation
48
    end
49

50
    def order(*args)
51
      relation = clone
52
      relation.order_values += args.flatten unless args.blank?
53
      relation
54
    end
55

56
    def reorder(*args)
57
      relation = clone
58
      unless args.blank?
59 60 61
        relation.order_values = args
        relation.reorder_flag = true
      end
62
      relation
63 64
    end

65
    def joins(*args)
66 67
      relation = clone

A
Aaron Patterson 已提交
68 69
      args.flatten!
      relation.joins_values += args unless args.blank?
70 71

      relation
P
Pratik Naik 已提交
72 73
    end

74
    def where(opts, *rest)
75
      relation = clone
76
      relation.where_values += build_where(opts, rest) unless opts.blank?
77
      relation
78
    end
P
Pratik Naik 已提交
79

80
    def having(*args)
81
      relation = clone
82
      relation.having_values += build_where(*args) unless args.blank?
83
      relation
84 85
    end

86
    def limit(value)
87 88 89
      relation = clone
      relation.limit_value = value
      relation
90 91
    end

92
    def offset(value)
93 94 95
      relation = clone
      relation.offset_value = value
      relation
96 97 98
    end

    def lock(locks = true)
99 100
      relation = clone

101
      case locks
102
      when String, TrueClass, NilClass
103
        relation.lock_value = locks || true
104
      else
105
        relation.lock_value = false
106
      end
107 108

      relation
109 110
    end

111
    def readonly(value = true)
112 113 114
      relation = clone
      relation.readonly_value = value
      relation
115 116
    end

117
    def create_with(value)
118 119 120
      relation = clone
      relation.create_with_value = value
      relation
121 122
    end

123
    def from(value)
124 125 126
      relation = clone
      relation.from_value = value
      relation
127 128
    end

129 130
    def extending(*modules, &block)
      modules << Module.new(&block) if block_given?
131 132

      relation = clone
133
      relation.send(:apply_modules, modules.flatten)
134
      relation
135 136
    end

137
    def reverse_order
N
Neeraj Singh 已提交
138
      order_clause = arel.order_clauses
139

N
Neeraj Singh 已提交
140
      order = order_clause.empty? ?
141
        "#{@klass.table_name}.#{@klass.primary_key} DESC" :
N
Neeraj Singh 已提交
142
        reverse_sql_order(order_clause).join(', ')
143

N
Neeraj Singh 已提交
144
      except(:order).order(Arel::SqlLiteral.new(order))
145 146
    end

147 148
    def arel
      @arel ||= build_arel
149 150
    end

151 152
    def custom_join_sql(*joins)
      arel = table
153

154 155 156 157 158 159 160 161 162
      joins.each do |join|
        next if join.blank?

        @implicit_readonly = true

        case join
        when Hash, Array, Symbol
          if array_of_strings?(join)
            join_string = join.join(' ')
163
            arel = arel.join(Arel::SqlLiteral.new(join_string))
164
          end
165 166
        when String
          arel = arel.join(Arel::SqlLiteral.new(join))
167 168 169 170
        else
          arel = arel.join(join)
        end
      end
171

172 173 174
      arel.joins(arel)
    end

175 176
    def build_arel
      arel = table
177

178
      arel = build_joins(arel, @joins_values) unless @joins_values.empty?
179

180
      (@where_values - ['']).uniq.each do |where|
181
        case where
182 183
        when Arel::SqlLiteral
          arel = arel.where(where)
184
        else
185
          sql = where.is_a?(String) ? where : where.to_sql(table.engine)
186
          arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
187
        end
188 189
      end

190
      arel = arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
191

192
      arel = arel.take(@limit_value) if @limit_value
193
      arel = arel.skip(@offset_value) if @offset_value
194

195
      arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
196

197
      arel = arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty?
198

199
      arel = build_select(arel, @select_values.uniq)
200

201
      arel = arel.from(@from_value) if @from_value
202
      arel = arel.lock(@lock_value) if @lock_value
203 204

      arel
205 206
    end

207
    def build_where(opts, other = [])
A
Aaron Patterson 已提交
208 209
      case opts
      when String, Array
210
        [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
A
Aaron Patterson 已提交
211 212
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
213
        PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
214
      else
215
        [opts]
216 217 218 219 220
      end
    end

    private

221 222 223
    def build_joins(relation, joins)
      association_joins = []

224
      joins = joins.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
225 226 227 228 229

      joins.each do |join|
        association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
      end

230
      stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
231

S
Santiago Pastorino 已提交
232
      non_association_joins = (joins - association_joins - stashed_association_joins)
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
      custom_joins = custom_join_sql(*non_association_joins)

      join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)

      join_dependency.graft(*stashed_association_joins)

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

      to_join = []

      join_dependency.join_associations.each do |association|
        if (association_relation = association.relation).is_a?(Array)
          to_join << [association_relation.first, association.join_class, association.association_join.first]
          to_join << [association_relation.last, association.join_class, association.association_join.last]
        else
          to_join << [association_relation, association.join_class, association.association_join]
        end
      end

252 253
      to_join.uniq.each do |left, join_class, right|
        relation = relation.join(left, join_class).on(*right)
254 255 256 257 258
      end

      relation.join(custom_joins)
    end

259
    def build_select(arel, selects)
260
      unless selects.empty?
261
        @implicit_readonly = false
262 263
        # TODO: fix this ugly hack, we should refactor the callers to get an Arel compatible array.
        # Before this change we were passing to Arel the last element only, and Arel is capable of handling an array
A
Aaron Patterson 已提交
264 265 266
        case select = selects.last
        when Arel::Expression, Arel::SqlLiteral
          arel.project(select)
267
        else
A
Aaron Patterson 已提交
268
          arel.project(*selects)
269 270
        end
      else
271
        arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*'))
272 273 274
      end
    end

P
Pratik Naik 已提交
275
    def apply_modules(modules)
276
      unless modules.empty?
277
        @extensions += modules
278
        modules.each {|extension| extend(extension) }
N
Neeraj Singh 已提交
279
      end
P
Pratik Naik 已提交
280 281
    end

282
    def reverse_sql_order(order_query)
N
Neeraj Singh 已提交
283
      order_query.join(', ').split(',').collect { |s|
284
        if s.match(/\s(asc|ASC)$/)
N
Neeraj Singh 已提交
285
          s.gsub(/\s(asc|ASC)$/, ' DESC')
286
        elsif s.match(/\s(desc|DESC)$/)
N
Neeraj Singh 已提交
287
          s.gsub(/\s(desc|DESC)$/, ' ASC')
288
        else
N
Neeraj Singh 已提交
289
          s + ' DESC'
290
        end
N
Neeraj Singh 已提交
291
      }
292 293
    end

P
Pratik Naik 已提交
294 295 296 297
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

298 299
  end
end