query_methods.rb 24.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 9
    Relation::MULTI_VALUE_METHODS.each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
10 11 12 13 14 15 16 17
        def #{name}_values                   # def select_values
          @values[:#{name}] || []            #   @values[:select] || []
        end                                  # end
                                             #
        def #{name}_values=(values)          # def select_values=(values)
          raise ImmutableRelation if @loaded #   raise ImmutableRelation if @loaded
          @values[:#{name}] = values         #   @values[:select] = values
        end                                  # end
18 19 20 21 22
      CODE
    end

    (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
23 24 25
        def #{name}_value                    # def readonly_value
          @values[:#{name}]                  #   @values[:readonly]
        end                                  # end
26 27 28
      CODE
    end

29 30 31 32 33 34 35
    Relation::SINGLE_VALUE_METHODS.each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
        def #{name}_value=(value)            # def readonly_value=(value)
          raise ImmutableRelation if @loaded #   raise ImmutableRelation if @loaded
          @values[:#{name}] = value          #   @values[:readonly] = value
        end                                  # end
      CODE
36 37
    end

O
Oscar Del Ben 已提交
38
    def create_with_value # :nodoc:
39
      @values[:create_with] || {}
40
    end
41 42

    alias extensions extending_values
43

O
Oscar Del Ben 已提交
44 45 46 47 48 49 50 51 52 53
    # Specify relationships to be included in the result set. For
    # example:
    #
    #   users = User.includes(:address)
    #   users.each do |user|
    #     user.address.city
    #   end
    #
    # allows you to access the +address+ attribute of the +User+ model without
    # firing an additional query. This will often result in a
54 55 56 57 58 59 60 61 62 63 64 65
    # performance improvement over a simple +join+.
    #
    # === conditions
    #
    # If you want to add conditions to your included models you'll have
    # to explicitly reference them. For example:
    #
    #   User.includes(:posts).where('posts.name = ?', 'example')
    #
    # Will throw an error, but this will work:
    #
    #   User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
66
    def includes(*args)
J
Jon Leighton 已提交
67
      args.empty? ? self : spawn.includes!(*args)
68
    end
69

70
    # Like #includes, but modifies the relation in place.
71
    def includes!(*args)
72
      args.reject! {|a| a.blank? }
A
Aaron Patterson 已提交
73

74 75
      self.includes_values = (includes_values + args).flatten.uniq
      self
76
    end
77

78 79 80 81 82 83
    # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
    #
    #   User.eager_load(:posts)
    #   => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
    #   FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
    #   "users"."id"
84
    def eager_load(*args)
J
Jon Leighton 已提交
85
      args.blank? ? self : spawn.eager_load!(*args)
86
    end
87

88
    # Like #eager_load, but modifies relation in place.
89
    def eager_load!(*args)
90 91
      self.eager_load_values += args
      self
92 93
    end

94 95 96 97
    # Allows preloading of +args+, in the same way that +includes+ does:
    #
    #   User.preload(:posts)
    #   => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
98
    def preload(*args)
J
Jon Leighton 已提交
99
      args.blank? ? self : spawn.preload!(*args)
100
    end
101

102
    # Like #preload, but modifies relation in place.
103
    def preload!(*args)
104 105
      self.preload_values += args
      self
106
    end
107

108 109 110 111 112 113 114 115 116
    # Used to indicate that an association is referenced by an SQL string, and should
    # therefore be JOINed in any query rather than loaded separately.
    #
    #   User.includes(:posts).where("posts.name = 'foo'")
    #   # => Doesn't JOIN the posts table, resulting in an error.
    #
    #   User.includes(:posts).where("posts.name = 'foo'").references(:posts)
    #   # => Query now knows the string references posts, so adds a JOIN
    def references(*args)
J
Jon Leighton 已提交
117
      args.blank? ? self : spawn.references!(*args)
118
    end
119

120
    # Like #references, but modifies relation in place.
121
    def references!(*args)
122 123 124
      args.flatten!

      self.references_values = (references_values + args.map!(&:to_s)).uniq
125
      self
126 127
    end

128
    # Works in two unique ways.
129
    #
130 131
    # First: takes a block so it can be used just like Array#select.
    #
132
    #   Model.all.select { |m| m.field == value }
133 134 135 136 137
    #
    # This will build an array of objects from the database for the scope,
    # converting them into an array and iterating through them using Array#select.
    #
    # Second: Modifies the SELECT statement for the query so that only certain
V
Vijay Dev 已提交
138
    # fields are retrieved:
139 140 141 142 143
    #
    #   >> Model.select(:field)
    #   => [#<Model field:value>]
    #
    # Although in the above example it looks as though this method returns an
V
Vijay Dev 已提交
144
    # array, it actually returns a relation object and can have other query
145 146
    # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
    #
147
    # The argument to the method can also be an array of fields.
148
    #
149
    #   >> Model.select([:field, :other_field, :and_one_more])
V
Vijay Dev 已提交
150
    #   => [#<Model field: "value", other_field: "value", and_one_more: "value">]
151
    #
152 153
    # Accessing attributes of an object that do not have fields retrieved by a select
    # will throw <tt>ActiveModel::MissingAttributeError</tt>:
154 155
    #
    #   >> Model.select(:field).first.other_field
156
    #   => ActiveModel::MissingAttributeError: missing attribute: other_field
157
    def select(value = Proc.new)
158
      if block_given?
159 160
        to_a.select { |*block_args| value.call(*block_args) }
      else
J
Jon Leighton 已提交
161
        spawn.select!(value)
162 163 164
      end
    end

165
    # Like #select, but modifies relation in place.
166
    def select!(value)
167 168
      self.select_values += Array.wrap(value)
      self
169
    end
S
Santiago Pastorino 已提交
170

O
Oscar Del Ben 已提交
171 172 173 174 175
    # Allows to specify a group attribute:
    #
    #   User.group(:name)
    #   => SELECT "users".* FROM "users" GROUP BY name
    #
176
    # Returns an array with distinct records based on the +group+ attribute:
O
Oscar Del Ben 已提交
177 178 179 180 181 182
    #
    #   User.select([:id, :name])
    #   => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
    #
    #   User.group(:name)
    #   => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
183
    def group(*args)
J
Jon Leighton 已提交
184
      args.blank? ? self : spawn.group!(*args)
185
    end
186

187
    # Like #group, but modifies relation in place.
188
    def group!(*args)
189 190 191
      args.flatten!

      self.group_values += args
192
      self
193
    end
194

O
Oscar Del Ben 已提交
195 196 197 198 199 200 201 202 203 204
    # Allows to specify an order attribute:
    #
    #   User.order('name')
    #   => SELECT "users".* FROM "users" ORDER BY name
    #
    #   User.order('name DESC')
    #   => SELECT "users".* FROM "users" ORDER BY name DESC
    #
    #   User.order('name DESC, email')
    #   => SELECT "users".* FROM "users" ORDER BY name DESC, email
205
    def order(*args)
J
Jon Leighton 已提交
206
      args.blank? ? self : spawn.order!(*args)
207
    end
208

209
    # Like #order, but modifies relation in place.
210
    def order!(*args)
211
      args.flatten!
212

213
      references = args.reject { |arg| Arel::Node === arg }
214
      references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
215
      references!(references) if references.any?
216

217
      self.order_values = args + self.order_values
218
      self
219
    end
220

221 222 223 224 225 226 227 228
    # Replaces any existing order defined on the relation with the specified order.
    #
    #   User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
    #
    # Subsequent calls to order on the same relation will be appended. For example:
    #
    #   User.order('email DESC').reorder('id ASC').order('name ASC')
    #
229
    # generates a query with 'ORDER BY name ASC, id ASC'.
S
Sebastian Martinez 已提交
230
    def reorder(*args)
J
Jon Leighton 已提交
231
      args.blank? ? self : spawn.reorder!(*args)
232
    end
233

234
    # Like #reorder, but modifies relation in place.
235
    def reorder!(*args)
236 237
      args.flatten!

238
      self.reordering_value = true
239
      self.order_values = args
240
      self
S
Sebastian Martinez 已提交
241 242
    end

243 244 245 246
    # Performs a joins on +args+:
    #
    #   User.joins(:posts)
    #   => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
247
    def joins(*args)
J
Jon Leighton 已提交
248
      args.compact.blank? ? self : spawn.joins!(*args)
249
    end
250

251
    # Like #joins, but modifies relation in place.
252
    def joins!(*args)
A
Aaron Patterson 已提交
253
      args.flatten!
254

255 256
      self.joins_values += args
      self
P
Pratik Naik 已提交
257 258
    end

A
Aaron Patterson 已提交
259
    def bind(value)
J
Jon Leighton 已提交
260
      spawn.bind!(value)
261 262
    end

263
    def bind!(value)
264 265
      self.bind_values += [value]
      self
A
Aaron Patterson 已提交
266 267
    end

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    # Returns a new relation, which is the result of filtering the current relation
    # according to the conditions in the arguments.
    #
    # #where accepts conditions in one of several formats. In the examples below, the resulting
    # SQL is given as an illustration; the actual query generated may be different depending
    # on the database adapter.
    #
    # === string
    #
    # A single string, without additional arguments, is passed to the query
    # constructor as a SQL fragment, and used in the where clause of the query.
    #
    #    Client.where("orders_count = '2'")
    #    # SELECT * from clients where orders_count = '2';
    #
    # Note that building your own string from user input may expose your application
    # to injection attacks if not done properly. As an alternative, it is recommended
    # to use one of the following methods.
    #
    # === array
    #
    # If an array is passed, then the first element of the array is treated as a template, and
    # the remaining elements are inserted into the template to generate the condition.
    # Active Record takes care of building the query to avoid injection attacks, and will
    # convert from the ruby type to the database type where needed. Elements are inserted
    # into the string in the order in which they appear.
    #
    #   User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # Alternatively, you can use named placeholders in the template, and pass a hash as the
    # second element of the array. The names in the template are replaced with the corresponding
    # values from the hash.
    #
    #   User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # This can make for more readable code in complex queries.
    #
    # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
    # than the previous methods; you are responsible for ensuring that the values in the template
    # are properly quoted. The values are passed to the connector for quoting, but the caller
    # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
    # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
    #
    #   User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # If #where is called with multiple arguments, these are treated as if they were passed as
    # the elements of a single array.
    #
    #   User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # When using strings to specify conditions, you can use any operator available from
    # the database. While this provides the most flexibility, you can also unintentionally introduce
    # dependencies on the underlying database. If your code is intended for general consumption,
    # test with multiple database backends.
    #
    # === hash
    #
    # #where will also accept a hash condition, in which the keys are fields and the values
    # are values to be searched for.
    #
    # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
    #
    #    User.where({ name: "Joe", email: "joe@example.com" })
    #    # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
    #
    #    User.where({ name: ["Alice", "Bob"]})
    #    # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
    #
    #    User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
    #    # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
    #
    # === Joins
    #
    # If the relation is the result of a join, you may create a condition which uses any of the
    # tables in the join. For string and array conditions, use the table name in the condition.
    #
    #    User.joins(:posts).where("posts.created_at < ?", Time.now)
    #
    # For hash conditions, you can either use the table name in the key, or use a sub-hash.
    #
    #    User.joins(:posts).where({ "posts.published" => true })
    #    User.joins(:posts).where({ :posts => { :published => true } })
    #
    # === empty condition
    #
    # If the condition returns true for blank?, then where is a no-op and returns the current relation.
358
    def where(opts, *rest)
J
Jon Leighton 已提交
359
      opts.blank? ? self : spawn.where!(opts, *rest)
360 361
    end

362 363 364
    # #where! is identical to #where, except that instead of returning a new relation, it adds
    # the condition to the existing relation.
    def where!(opts, *rest)
365
      references!(PredicateBuilder.references(opts)) if Hash === opts
366

367 368
      self.where_values += build_where(opts, rest)
      self
369
    end
P
Pratik Naik 已提交
370

371 372 373 374
    # Allows to specify a HAVING clause. Note that you can't use HAVING
    # without also specifying a GROUP clause.
    #
    #   Order.having('SUM(price) > 30').group('user_id')
375
    def having(opts, *rest)
J
Jon Leighton 已提交
376
      opts.blank? ? self : spawn.having!(opts, *rest)
377 378
    end

379
    # Like #having, but modifies relation in place.
380
    def having!(opts, *rest)
381
      references!(PredicateBuilder.references(opts)) if Hash === opts
382

383 384
      self.having_values += build_where(opts, rest)
      self
385 386
    end

387
    # Specifies a limit for the number of records to retrieve.
388 389 390 391
    #
    #   User.limit(10) # generated SQL has 'LIMIT 10'
    #
    #   User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
392
    def limit(value)
J
Jon Leighton 已提交
393
      spawn.limit!(value)
394 395
    end

396
    # Like #limit, but modifies relation in place.
397
    def limit!(value)
398 399
      self.limit_value = value
      self
400 401
    end

402 403 404 405
    # Specifies the number of rows to skip before returning rows.
    #
    #   User.offset(10) # generated SQL has "OFFSET 10"
    #
406
    # Should be used with order.
407
    #
408
    #   User.offset(10).order("name ASC")
409
    def offset(value)
J
Jon Leighton 已提交
410
      spawn.offset!(value)
411 412
    end

413
    # Like #offset, but modifies relation in place.
414
    def offset!(value)
415 416
      self.offset_value = value
      self
417 418
    end

419
    # Specifies locking settings (default to +true+). For more information
420
    # on locking, please see +ActiveRecord::Locking+.
421
    def lock(locks = true)
J
Jon Leighton 已提交
422
      spawn.lock!(locks)
423
    end
424

425
    # Like #lock, but modifies relation in place.
426
    def lock!(locks = true)
427
      case locks
428
      when String, TrueClass, NilClass
429
        self.lock_value = locks || true
430
      else
431
        self.lock_value = false
432
      end
433

434
      self
435 436
    end

437
    # Returns a chainable relation with zero records, specifically an
V
Vijay Dev 已提交
438
    # instance of the <tt>ActiveRecord::NullRelation</tt> class.
439
    #
V
Vijay Dev 已提交
440 441 442
    # The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
    # Null Object pattern. It is an object with defined null behavior and always returns an empty
    # array of records without quering the database.
443 444 445 446
    #
    # Any subsequent condition chained to the returned relation will continue
    # generating an empty relation and will not fire any query to the database.
    #
447 448
    # Used in cases where a method or scope could return zero records but the
    # result needs to be chainable.
449 450 451 452
    #
    # For example:
    #
    #   @posts = current_user.visible_posts.where(:name => params[:name])
453
    #   # => the visible_posts method is expected to return a chainable Relation
454 455 456
    #
    #   def visible_posts
    #     case role
457
    #     when 'Country Manager'
458
    #       Post.where(:country => country)
459
    #     when 'Reviewer'
460
    #       Post.published
461
    #     when 'Bad User'
462 463 464 465 466
    #       Post.none # => returning [] instead breaks the previous code
    #     end
    #   end
    #
    def none
467
      extending(NullRelation)
468 469
    end

470 471 472 473 474 475
    # Sets readonly attributes for the returned relation. If value is
    # true (default), attempting to update a record will result in an error.
    #
    #   users = User.readonly
    #   users.first.save
    #   => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
476
    def readonly(value = true)
J
Jon Leighton 已提交
477
      spawn.readonly!(value)
478 479
    end

480
    # Like #readonly, but modifies relation in place.
481
    def readonly!(value = true)
482 483
      self.readonly_value = value
      self
484 485
    end

486 487 488 489 490 491 492 493 494 495 496 497 498
    # Sets attributes to be used when creating new records from a
    # relation object.
    #
    #   users = User.where(name: 'Oscar')
    #   users.new.name # => 'Oscar'
    #
    #   users = users.create_with(name: 'DHH')
    #   users.new.name # => 'DHH'
    #
    # You can pass +nil+ to +create_with+ to reset attributes:
    #
    #   users = users.create_with(nil)
    #   users.new.name # => 'Oscar'
499
    def create_with(value)
J
Jon Leighton 已提交
500
      spawn.create_with!(value)
501 502
    end

503 504 505
    # Like #create_with but modifies the relation in place. Raises
    # +ImmutableRelation+ if the relation has already been loaded.
    #
506
    #   users = User.all.create_with!(name: 'Oscar')
V
Vijay Dev 已提交
507
    #   users.new.name # => 'Oscar'
508
    def create_with!(value)
509 510
      self.create_with_value = value ? create_with_value.merge(value) : {}
      self
511 512
    end

513 514 515 516 517 518 519
    # Specifies table from which the records will be fetched. For example:
    #
    #   Topic.select('title').from('posts')
    #   #=> SELECT title FROM posts
    #
    # Can accept other relation objects. For example:
    #
520
    #   Topic.select('title').from(Topic.approved)
521 522
    #   # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
    #
523
    #   Topic.select('a.title').from(Topic.approved, :a)
524 525 526 527
    #   # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
528 529
    end

530
    # Like #from, but modifies relation in place.
531
    def from!(value, subquery_name = nil)
532
      self.from_value = [value, subquery_name]
533
      self
534 535
    end

536 537 538 539 540 541 542 543 544 545 546
    # Specifies whether the records should be unique or not. For example:
    #
    #   User.select(:name)
    #   # => Might return two records with the same name
    #
    #   User.select(:name).uniq
    #   # => Returns 1 record per unique name
    #
    #   User.select(:name).uniq.uniq(false)
    #   # => You can also remove the uniqueness
    def uniq(value = true)
J
Jon Leighton 已提交
547
      spawn.uniq!(value)
548 549
    end

550
    # Like #uniq, but modifies relation in place.
551
    def uniq!(value = true)
552 553
      self.uniq_value = value
      self
554 555
    end

556
    # Used to extend a scope with additional methods, either through
557 558
    # a module or through a block provided.
    #
559 560 561 562 563 564 565 566 567 568
    # The object returned is a relation, which can be further extended.
    #
    # === Using a module
    #
    #   module Pagination
    #     def page(number)
    #       # pagination code goes here
    #     end
    #   end
    #
569
    #   scope = Model.all.extending(Pagination)
570 571
    #   scope.page(params[:page])
    #
V
Vijay Dev 已提交
572
    # You can also pass a list of modules:
573
    #
574
    #   scope = Model.all.extending(Pagination, SomethingElse)
575 576 577
    #
    # === Using a block
    #
578
    #   scope = Model.all.extending do
579
    #     def page(number)
580
    #       # pagination code goes here
581 582 583 584 585 586
    #     end
    #   end
    #   scope.page(params[:page])
    #
    # You can also use a block and a module list:
    #
587
    #   scope = Model.all.extending(Pagination) do
588
    #     def per_page(number)
589
    #       # pagination code goes here
590 591
    #     end
    #   end
592 593
    def extending(*modules, &block)
      if modules.any? || block
J
Jon Leighton 已提交
594
        spawn.extending!(*modules, &block)
595 596 597 598
      else
        self
      end
    end
599

600
    # Like #extending, but modifies relation in place.
601
    def extending!(*modules, &block)
602
      modules << Module.new(&block) if block_given?
603

J
Jon Leighton 已提交
604
      self.extending_values += modules.flatten
605
      extend(*extending_values) if extending_values.any?
606

607
      self
608 609
    end

610 611 612
    # Reverse the existing order clause on the relation.
    #
    #   User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
613
    def reverse_order
J
Jon Leighton 已提交
614
      spawn.reverse_order!
615 616
    end

617
    # Like #reverse_order, but modifies relation in place.
618
    def reverse_order!
619 620
      self.reverse_order_value = !reverse_order_value
      self
621 622
    end

623
    # Returns the Arel object associated with the relation.
624
    def arel
625
      @arel ||= with_default_scope.build_arel
626 627
    end

628
    # Like #arel, but ignores the default scope of the model.
629
    def build_arel
630
      arel = Arel::SelectManager.new(table.engine, table)
631

632
      build_joins(arel, joins_values) unless joins_values.empty?
633

634
      collapse_wheres(arel, (where_values - ['']).uniq)
635

636
      arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
637

638 639
      arel.take(connection.sanitize_limit(limit_value)) if limit_value
      arel.skip(offset_value.to_i) if offset_value
A
Aaron Patterson 已提交
640

641
      arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
642

643 644
      order = order_values
      order = reverse_sql_order(order) if reverse_order_value
645
      arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
646

647
      build_select(arel, select_values.uniq)
648

649
      arel.distinct(uniq_value)
650
      arel.from(build_from) if from_value
651
      arel.lock(lock_value) if lock_value
652 653

      arel
654 655
    end

656 657
    private

658
    def custom_join_ast(table, joins)
659 660
      joins = joins.reject { |join| join.blank? }

661
      return [] if joins.empty?
662 663 664

      @implicit_readonly = true

665
      joins.map do |join|
666 667 668 669 670 671
        case join
        when Array
          join = Arel.sql(join.join(' ')) if array_of_strings?(join)
        when String
          join = Arel.sql(join)
        end
672
        table.create_string_join(join)
673 674 675
      end
    end

676 677 678
    def collapse_wheres(arel, wheres)
      equalities = wheres.grep(Arel::Nodes::Equality)

A
Aaron Patterson 已提交
679
      arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
680 681 682

      (wheres - equalities).each do |where|
        where = Arel.sql(where) if String === where
683
        arel.where(Arel::Nodes::Grouping.new(where))
684 685 686
      end
    end

687
    def build_where(opts, other = [])
A
Aaron Patterson 已提交
688 689
      case opts
      when String, Array
690
        [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
A
Aaron Patterson 已提交
691
      when Hash
692 693
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
        PredicateBuilder.build_from_hash(table.engine, attributes, table)
694
      else
695
        [opts]
696 697 698
      end
    end

699 700 701 702 703 704 705 706 707 708 709
    def build_from
      opts, name = from_value
      case opts
      when Relation
        name ||= 'subquery'
        opts.arel.as(name.to_s)
      else
        opts
      end
    end

710
    def build_joins(manager, joins)
A
Aaron Patterson 已提交
711 712 713 714 715 716
      buckets = joins.group_by do |join|
        case join
        when String
          'string_join'
        when Hash, Symbol, Array
          'association_join'
717
        when ActiveRecord::Associations::JoinDependency::JoinAssociation
A
Aaron Patterson 已提交
718
          'stashed_join'
719 720
        when Arel::Nodes::Join
          'join_node'
A
Aaron Patterson 已提交
721 722 723
        else
          raise 'unknown class: %s' % join.class.name
        end
724 725
      end

A
Aaron Patterson 已提交
726 727
      association_joins         = buckets['association_join'] || []
      stashed_association_joins = buckets['stashed_join'] || []
728
      join_nodes                = (buckets['join_node'] || []).uniq
A
Aaron Patterson 已提交
729 730 731
      string_joins              = (buckets['string_join'] || []).map { |x|
        x.strip
      }.uniq
732

733
      join_list = join_nodes + custom_join_ast(manager, string_joins)
734

735
      join_dependency = ActiveRecord::Associations::JoinDependency.new(
736 737 738 739
        @klass,
        association_joins,
        join_list
      )
740 741 742 743 744

      join_dependency.graft(*stashed_association_joins)

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

A
Aaron Patterson 已提交
745
      # FIXME: refactor this to build an AST
746
      join_dependency.join_associations.each do |association|
747
        association.join_to(manager)
748 749
      end

750
      manager.join_sources.concat join_list
751 752

      manager
753 754
    end

755
    def build_select(arel, selects)
756
      unless selects.empty?
757
        @implicit_readonly = false
758
        arel.project(*selects)
759
      else
760
        arel.project(@klass.arel_table[Arel.star])
761 762 763
      end
    end

764
    def reverse_sql_order(order_query)
B
Brian Mathiyakom 已提交
765 766
      order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?

767 768
      order_query.map do |o|
        case o
769
        when Arel::Nodes::Ordering
770 771
          o.reverse
        when String, Symbol
772 773 774 775
          o.to_s.split(',').collect do |s|
            s.strip!
            s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
          end
776 777 778 779
        else
          o
        end
      end.flatten
780 781
    end

P
Pratik Naik 已提交
782 783 784 785
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

786 787
  end
end