query_methods.rb 8.1 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 15 16 17
      args.reject! {|a| a.blank? }

      relation = clone
      relation.includes_values = (relation.includes_values + args).flatten.uniq if args.present?
      relation
18
    end
19

20
    def eager_load(*args)
21 22 23
      relation = clone
      relation.eager_load_values += args if args.present?
      relation
24 25 26
    end

    def preload(*args)
27 28 29
      relation = clone
      relation.preload_values += args if args.present?
      relation
30
    end
31

A
Aaron Patterson 已提交
32
    def select(value = nil)
33
      if block_given?
P
Pratik Naik 已提交
34
        to_a.select {|*block_args| yield(*block_args) }
35
      else
36
        relation = clone
A
Aaron Patterson 已提交
37
        relation.select_values += [value] if value
38
        relation
S
Santiago Pastorino 已提交
39
      end
40
    end
S
Santiago Pastorino 已提交
41

42
    def group(*args)
43 44 45
      relation = clone
      relation.group_values += args.flatten if args.present?
      relation
46
    end
47

48
    def order(*args)
49 50 51
      relation = clone
      relation.order_values += args.flatten if args.present?
      relation
52
    end
53

54
    def reorder(*args)
55
      relation = clone
56 57 58 59
      if args.present?
        relation.order_values = args
        relation.reorder_flag = true
      end
60
      relation
61 62
    end

63
    def joins(*args)
64 65
      relation = clone

66 67
      if args.present?
        args.flatten!
68
        relation.joins_values += args if args.present?
69
      end
70 71

      relation
P
Pratik Naik 已提交
72 73
    end

74
    def where(opts, *rest)
75 76
      relation = clone

77
      if opts.present? && value = build_where(opts, rest)
78
        relation.where_values += Array.wrap(value)
79
      end
80 81

      relation
82
    end
P
Pratik Naik 已提交
83

84
    def having(*args)
85 86 87 88
      relation = clone

      if args.present? && value = build_where(*args)
        relation.having_values += Array.wrap(value)
89
      end
90 91

      relation
92 93
    end

94
    def limit(value)
95 96 97
      relation = clone
      relation.limit_value = value
      relation
98 99
    end

100
    def offset(value)
101 102 103
      relation = clone
      relation.offset_value = value
      relation
104 105 106
    end

    def lock(locks = true)
107 108
      relation = clone

109
      case locks
110
      when String, TrueClass, NilClass
111
        relation.lock_value = locks || true
112
      else
113
        relation.lock_value = false
114
      end
115 116

      relation
117 118
    end

119
    def readonly(value = true)
120 121 122
      relation = clone
      relation.readonly_value = value
      relation
123 124
    end

125
    def create_with(value)
126 127 128
      relation = clone
      relation.create_with_value = value
      relation
129 130
    end

131
    def from(value)
132 133 134
      relation = clone
      relation.from_value = value
      relation
135 136
    end

137 138
    def extending(*modules, &block)
      modules << Module.new(&block) if block_given?
139 140 141 142

      relation = clone
      relation.send(:apply_modules, *modules)
      relation
143 144
    end

145
    def reverse_order
146
      order_clause = arel.order_clauses.join(', ')
147 148
      relation = except(:order)

149 150 151 152
      order = order_clause.blank? ?
        "#{@klass.table_name}.#{@klass.primary_key} DESC" :
        reverse_sql_order(order_clause)

153
      relation.order(Arel::SqlLiteral.new(order))
154 155
    end

156 157
    def arel
      @arel ||= build_arel
158 159
    end

160 161
    def custom_join_sql(*joins)
      arel = table
162

163 164 165 166 167 168 169 170 171
      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(' ')
172
            arel = arel.join(Arel::SqlLiteral.new(join_string))
173
          end
174 175
        when String
          arel = arel.join(Arel::SqlLiteral.new(join))
176 177 178 179
        else
          arel = arel.join(join)
        end
      end
180

181 182 183
      arel.joins(arel)
    end

184 185
    def build_arel
      arel = table
186

187
      arel = build_joins(arel, @joins_values) unless @joins_values.empty?
188

189
      (@where_values - ['']).uniq.each do |where|
190
        case where
191 192
        when Arel::SqlLiteral
          arel = arel.where(where)
193
        else
194 195
          sql = where.is_a?(String) ? where : where.to_sql
          arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
196
        end
197 198
      end

199
      arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?
200

201
      arel = arel.take(@limit_value) if @limit_value
202
      arel = arel.skip(@offset_value) if @offset_value
203

204
      arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?
205

206
      arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?
207

208
      arel = build_select(arel, @select_values.uniq)
209

210
      arel = arel.from(@from_value) if @from_value
211
      arel = arel.lock(@lock_value) if @lock_value
212 213

      arel
214 215
    end

216
    def build_where(opts, other = [])
A
Aaron Patterson 已提交
217 218
      case opts
      when String, Array
219
        @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
A
Aaron Patterson 已提交
220 221
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
222
        PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
223
      else
A
Aaron Patterson 已提交
224
        opts
225 226 227 228 229
      end
    end

    private

230 231 232 233 234 235 236 237 238 239
    def build_joins(relation, joins)
      joined_associations = []
      association_joins = []

      joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq

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

240
      stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
241

S
Santiago Pastorino 已提交
242
      non_association_joins = (joins - association_joins - stashed_association_joins)
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
      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

      to_join.each do |tj|
        unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
          joined_associations << tj
          relation = relation.join(tj[0], tj[1]).on(*tj[2])
        end
      end

      relation.join(custom_joins)
    end

272
    def build_select(arel, selects)
273
      unless selects.empty?
274
        @implicit_readonly = false
275 276
        # 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 已提交
277 278 279 280 281
        case select = selects.last
        when Arel::Expression, Arel::SqlLiteral
          arel.project(select)
        when /^COUNT\(/
          arel.project(Arel::SqlLiteral.new(select))
282
        else
A
Aaron Patterson 已提交
283
          arel.project(*selects)
284 285
        end
      else
286
        arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*'))
287 288 289
      end
    end

P
Pratik Naik 已提交
290 291 292 293 294 295
    def apply_modules(modules)
      values = Array.wrap(modules)
      @extensions += values if values.present?
      values.each {|extension| extend(extension) }
    end

296
    def reverse_sql_order(order_query)
297
      order_query.split(',').each { |s|
298 299 300 301 302 303 304 305 306 307
        if s.match(/\s(asc|ASC)$/)
          s.gsub!(/\s(asc|ASC)$/, ' DESC')
        elsif s.match(/\s(desc|DESC)$/)
          s.gsub!(/\s(desc|DESC)$/, ' ASC')
        else
          s.concat(' DESC')
        end
      }.join(',')
    end

P
Pratik Naik 已提交
308 309 310 311
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

312 313
  end
end