query_methods.rb 24.8 KB
Newer Older
1
require 'active_support/core_ext/array/wrap'
2

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

7 8
    Relation::MULTI_VALUE_METHODS.each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
9 10 11 12 13 14 15 16
        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
17 18 19 20 21
      CODE
    end

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

28 29 30 31 32 33 34
    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
35 36
    end

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

    alias extensions extending_values
42

O
Oscar Del Ben 已提交
43 44 45 46 47 48 49 50 51 52
    # 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
53 54 55 56 57 58 59 60 61 62 63 64
    # 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)
65
    def includes(*args)
J
Jon Leighton 已提交
66
      args.empty? ? self : spawn.includes!(*args)
67
    end
68

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

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

77 78 79 80 81 82
    # 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"
83
    def eager_load(*args)
J
Jon Leighton 已提交
84
      args.blank? ? self : spawn.eager_load!(*args)
85
    end
86

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

93 94 95 96
    # 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)
97
    def preload(*args)
J
Jon Leighton 已提交
98
      args.blank? ? self : spawn.preload!(*args)
99
    end
100

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

107 108 109 110 111 112 113 114 115
    # 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 已提交
116
      args.blank? ? self : spawn.references!(*args)
117
    end
118

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

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

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

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

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

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

      self.group_values += args
191
      self
192
    end
193

O
Oscar Del Ben 已提交
194 195 196 197 198 199 200 201 202 203
    # 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
204
    def order(*args)
J
Jon Leighton 已提交
205
      args.blank? ? self : spawn.order!(*args)
206
    end
207

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

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

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

220 221 222 223 224 225 226 227
    # 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')
    #
228
    # generates a query with 'ORDER BY name ASC, id ASC'.
S
Sebastian Martinez 已提交
229
    def reorder(*args)
J
Jon Leighton 已提交
230
      args.blank? ? self : spawn.reorder!(*args)
231
    end
232

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

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

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

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

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

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

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

267 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
    # 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.
357
    def where(opts, *rest)
J
Jon Leighton 已提交
358
      opts.blank? ? self : spawn.where!(opts, *rest)
359 360
    end

361 362 363
    # #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)
364
      references!(PredicateBuilder.references(opts)) if Hash === opts
365

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

370 371 372 373
    # 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')
374
    def having(opts, *rest)
J
Jon Leighton 已提交
375
      opts.blank? ? self : spawn.having!(opts, *rest)
376 377
    end

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

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

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

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

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

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

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

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

433
      self
434 435
    end

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

469 470 471 472 473 474
    # 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
475
    def readonly(value = true)
J
Jon Leighton 已提交
476
      spawn.readonly!(value)
477 478
    end

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

485 486 487 488 489 490 491 492 493 494 495 496 497
    # 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'
498
    def create_with(value)
J
Jon Leighton 已提交
499
      spawn.create_with!(value)
500 501
    end

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

512 513 514 515 516 517 518
    # 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:
    #
519
    #   Topic.select('title').from(Topic.approved)
520 521
    #   # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
    #
522
    #   Topic.select('a.title').from(Topic.approved, :a)
523 524 525 526
    #   # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
527 528
    end

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

535 536 537 538 539 540 541 542 543 544 545
    # 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 已提交
546
      spawn.uniq!(value)
547 548
    end

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

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

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

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

606
      self
607 608
    end

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

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

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

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

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

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

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

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

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

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

646
      build_select(arel, select_values.uniq)
647

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

      arel
653 654
    end

655 656
    private

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

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

      @implicit_readonly = true

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

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

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

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

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

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

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

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

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

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

      join_dependency.graft(*stashed_association_joins)

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

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

749
      manager.join_sources.concat join_list
750 751

      manager
752 753
    end

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

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

766 767
      order_query.map do |o|
        case o
768
        when Arel::Nodes::Ordering
769 770
          o.reverse
        when String, Symbol
771 772 773 774
          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
775 776 777 778
        else
          o
        end
      end.flatten
779 780
    end

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

785 786
  end
end