query_methods.rb 47.9 KB
Newer Older
1 2
# frozen_string_literal: true

3 4 5 6
require "active_record/relation/from_clause"
require "active_record/relation/query_attribute"
require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
7
require "active_model/forbidden_attributes_protection"
8

9 10
module ActiveRecord
  module QueryMethods
11 12
    extend ActiveSupport::Concern

13 14
    include ActiveModel::ForbiddenAttributesProtection

15
    # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
    # In this case, #where must be chained with #not to return a new relation.
17
    class WhereChain
18 19
      include ActiveModel::ForbiddenAttributesProtection

20 21 22 23
      def initialize(scope)
        @scope = scope
      end

24 25
      # Returns a new relation expressing WHERE + NOT condition according to
      # the conditions in the arguments.
26
      #
27
      # #not accepts conditions as a string, array, or hash. See QueryMethods#where for
28
      # more details on each format.
29 30
      #
      #    User.where.not("name = 'Jon'")
31
      #    # SELECT * FROM users WHERE NOT (name = 'Jon')
32 33
      #
      #    User.where.not(["name = ?", "Jon"])
34
      #    # SELECT * FROM users WHERE NOT (name = 'Jon')
35
      #
36
      #    User.where.not(name: "Jon")
37
      #    # SELECT * FROM users WHERE name != 'Jon'
38 39 40 41
      #
      #    User.where.not(name: nil)
      #    # SELECT * FROM users WHERE name IS NOT NULL
      #
42
      #    User.where.not(name: %w(Ko1 Nobu))
43 44
      #    # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
      def not(opts, *rest)
45 46
        opts = sanitize_forbidden_attributes(opts)

47
        where_clause = @scope.send(:where_clause_factory).build(opts, rest)
48 49

        @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
50 51 52 53

        if not_behaves_as_nor?(opts)
          ActiveSupport::Deprecation.warn(<<~MSG.squish)
            NOT conditions will no longer behave as NOR in Rails 6.1.
54
            To continue using NOR conditions, NOT each condition individually
55 56 57 58 59 60 61 62 63
            (`#{
              opts.flat_map { |key, value|
                if value.is_a?(Hash) && value.size > 1
                  value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
                else
                  ".where.not(#{key.inspect} => ...)"
                end
              }.join
            }`).
64 65 66 67 68 69
          MSG
          @scope.where_clause += where_clause.invert(:nor)
        else
          @scope.where_clause += where_clause.invert
        end

70 71
        @scope
      end
72

G
Godfrey Chan 已提交
73
      # Returns a new relation with left outer joins and where clause to identify
74 75 76 77 78
      # missing relations.
      #
      # For example, posts that are missing a related author:
      #
      #    Post.where.missing(:author)
79 80 81
      #    # SELECT "posts".* FROM "posts"
      #    # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
      #    # WHERE "authors"."id" IS NULL
82
      #
83
      # Additionally, multiple relations can be combined. This will return posts
84
      # that are missing both an author and any comments:
85 86
      #
      #    Post.where.missing(:author, :comments)
87 88 89 90
      #    # SELECT "posts".* FROM "posts"
      #    # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
      #    # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
      #    # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
91 92 93 94 95 96 97 98 99 100 101
      def missing(*args)
        args.each do |arg|
          reflection = @scope.klass._reflect_on_association(arg)
          opts = { reflection.table_name => { reflection.association_primary_key => nil } }
          @scope.left_outer_joins!(arg)
          @scope.where!(opts)
        end

        @scope
      end

102 103
      private
        def not_behaves_as_nor?(opts)
104 105 106 107
          return false unless opts.is_a?(Hash)

          opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
            opts.size > 1
108
        end
109 110
    end

111
    FROZEN_EMPTY_ARRAY = [].freeze
112
    FROZEN_EMPTY_HASH = {}.freeze
113

114
    Relation::VALUE_METHODS.each do |name|
R
Ryuta Kamizono 已提交
115
      method_name, default =
116
        case name
R
Ryuta Kamizono 已提交
117 118 119 120 121
        when *Relation::MULTI_VALUE_METHODS
          ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
        when *Relation::SINGLE_VALUE_METHODS
          ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
        when *Relation::CLAUSE_METHODS
122
          ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
123
        end
R
Ryuta Kamizono 已提交
124

125
      class_eval <<-CODE, __FILE__, __LINE__ + 1
R
Ryuta Kamizono 已提交
126 127 128 129 130 131 132 133
        def #{method_name}                     # def includes_values
          @values.fetch(:#{name}, #{default})  #   @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
        end                                    # end

        def #{method_name}=(value)             # def includes_values=(value)
          assert_mutability!                   #   assert_mutability!
          @values[:#{name}] = value            #   @values[:includes] = value
        end                                    # end
134
      CODE
135 136
    end

137
    alias extensions extending_values
138

O
Oscar Del Ben 已提交
139 140 141 142 143 144 145 146 147 148
    # 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
149
    # performance improvement over a simple join.
150
    #
151 152 153 154 155 156 157 158
    # You can also specify multiple relationships, like this:
    #
    #   users = User.includes(:address, :friends)
    #
    # Loading nested relationships is possible using a Hash:
    #
    #   users = User.includes(:address, friends: [:address, :followers])
    #
159 160
    # === conditions
    #
161
    # If you want to add string conditions to your included models, you'll have
162 163 164 165 166 167 168
    # 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)
169
    #
170
    # Note that #includes works with association names while #references needs
171
    # the actual table name.
172 173 174 175 176
    #
    # If you pass the conditions via hash, you don't need to call #references
    # explicitly, as #where references the tables for you. For example, this
    # will work correctly:
    #
177
    #   User.includes(:posts).where(posts: { name: 'example' })
178
    def includes(*args)
179
      check_if_method_has_arguments!(:includes, args)
180
      spawn.includes!(*args)
181
    end
182

J
Jon Leighton 已提交
183
    def includes!(*args) # :nodoc:
184
      args.compact_blank!
185
      args.flatten!
A
Aaron Patterson 已提交
186

187
      self.includes_values |= args
188
      self
189
    end
190

191 192 193
    # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
    #
    #   User.eager_load(:posts)
194 195 196
    #   # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
    #   # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
    #   # "users"."id"
197
    def eager_load(*args)
198
      check_if_method_has_arguments!(:eager_load, args)
199
      spawn.eager_load!(*args)
200
    end
201

J
Jon Leighton 已提交
202
    def eager_load!(*args) # :nodoc:
203
      self.eager_load_values |= args
204
      self
205 206
    end

207
    # Allows preloading of +args+, in the same way that #includes does:
208 209
    #
    #   User.preload(:posts)
210
    #   # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
211
    def preload(*args)
212
      check_if_method_has_arguments!(:preload, args)
213
      spawn.preload!(*args)
214
    end
215

J
Jon Leighton 已提交
216
    def preload!(*args) # :nodoc:
217
      self.preload_values |= args
218
      self
219
    end
220

221 222 223 224 225 226 227 228 229 230 231 232 233
    # Extracts a named +association+ from the relation. The named association is first preloaded,
    # then the individual association records are collected from the relation. Like so:
    #
    #   account.memberships.extract_associated(:user)
    #   # => Returns collection of User records
    #
    # This is short-hand for:
    #
    #   account.memberships.preload(:user).collect(&:user)
    def extract_associated(association)
      preload(association).collect(&association)
    end

234 235
    # Use to indicate that the given +table_names+ are referenced by an SQL string,
    # and should therefore be JOINed in any query rather than loaded separately.
236
    # This method only works in conjunction with #includes.
237
    # See #includes for more details.
238 239
    #
    #   User.includes(:posts).where("posts.name = 'foo'")
240
    #   # Doesn't JOIN the posts table, resulting in an error.
241 242
    #
    #   User.includes(:posts).where("posts.name = 'foo'").references(:posts)
243
    #   # Query now knows the string references posts, so adds a JOIN
244 245 246
    def references(*table_names)
      check_if_method_has_arguments!(:references, table_names)
      spawn.references!(*table_names)
247
    end
248

249 250 251
    def references!(*table_names) # :nodoc:
      table_names.flatten!
      table_names.map!(&:to_s)
252

253
      self.references_values |= table_names
254
      self
255 256
    end

257
    # Works in two unique ways.
258
    #
259
    # First: takes a block so it can be used just like <tt>Array#select</tt>.
260
    #
261
    #   Model.all.select { |m| m.field == value }
262 263
    #
    # This will build an array of objects from the database for the scope,
264 265
    # converting them into an array and iterating through them using
    # <tt>Array#select</tt>.
266 267
    #
    # Second: Modifies the SELECT statement for the query so that only certain
V
Vijay Dev 已提交
268
    # fields are retrieved:
269
    #
270
    #   Model.select(:field)
271
    #   # => [#<Model id: nil, field: "value">]
272 273
    #
    # Although in the above example it looks as though this method returns an
V
Vijay Dev 已提交
274
    # array, it actually returns a relation object and can have other query
275 276
    # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
    #
277
    # The argument to the method can also be an array of fields.
278
    #
279
    #   Model.select(:field, :other_field, :and_one_more)
280
    #   # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
281
    #
282 283 284
    # You can also use one or more strings, which will be used unchanged as SELECT fields.
    #
    #   Model.select('field AS field_one', 'other_field AS field_two')
285
    #   # => [#<Model id: nil, field: "value", other_field: "value">]
286 287 288 289 290 291
    #
    # If an alias was specified, it will be accessible from the resulting objects:
    #
    #   Model.select('field AS field_one').first.field_one
    #   # => "value"
    #
292
    # Accessing attributes of an object that do not have fields retrieved by a select
293
    # except +id+ will throw ActiveModel::MissingAttributeError:
294
    #
295 296 297
    #   Model.select(:field).first.other_field
    #   # => ActiveModel::MissingAttributeError: missing attribute: other_field
    def select(*fields)
298 299
      if block_given?
        if fields.any?
300
          raise ArgumentError, "`select' with block doesn't take arguments."
301 302 303 304 305
        end

        return super()
      end

306
      raise ArgumentError, "Call `select' with at least one field" if fields.empty?
307
      spawn._select!(*fields)
308 309
    end

310
    def _select!(*fields) # :nodoc:
311
      fields.compact_blank!
312 313
      fields.flatten!
      self.select_values += fields
314
      self
315
    end
S
Santiago Pastorino 已提交
316

W
Willian Gustavo Veiga 已提交
317 318 319
    # Allows you to change a previously set select statement.
    #
    #   Post.select(:title, :body)
320
    #   # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
W
Willian Gustavo Veiga 已提交
321 322
    #
    #   Post.select(:title, :body).reselect(:created_at)
323
    #   # SELECT `posts`.`created_at` FROM `posts`
W
Willian Gustavo Veiga 已提交
324 325 326
    #
    # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
    # Note that we're unscoping the entire select statement.
327 328 329 330 331 332 333 334 335
    def reselect(*args)
      check_if_method_has_arguments!(:reselect, args)
      spawn.reselect!(*args)
    end

    # Same as #reselect but operates on relation in-place instead of copying.
    def reselect!(*args) # :nodoc:
      self.select_values = args
      self
W
Willian Gustavo Veiga 已提交
336
    end
S
Santiago Pastorino 已提交
337

O
Oscar Del Ben 已提交
338 339 340
    # Allows to specify a group attribute:
    #
    #   User.group(:name)
341
    #   # SELECT "users".* FROM "users" GROUP BY name
O
Oscar Del Ben 已提交
342
    #
343
    # Returns an array with distinct records based on the +group+ attribute:
O
Oscar Del Ben 已提交
344 345
    #
    #   User.select([:id, :name])
346
    #   # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
O
Oscar Del Ben 已提交
347 348
    #
    #   User.group(:name)
349
    #   # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
350 351
    #
    #   User.group('name AS grouped_name, age')
352
    #   # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
353 354
    #
    # Passing in an array of attributes to group by is also supported.
355
    #
356
    #   User.select([:id, :first_name]).group(:id, :first_name).first(3)
357
    #   # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
358
    def group(*args)
359
      check_if_method_has_arguments!(:group, args)
360
      spawn.group!(*args)
361
    end
362

J
Jon Leighton 已提交
363
    def group!(*args) # :nodoc:
364 365
      args.flatten!

366
      self.group_values |= args
367
      self
368
    end
369

O
Oscar Del Ben 已提交
370 371
    # Allows to specify an order attribute:
    #
372
    #   User.order(:name)
373
    #   # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
374
    #
375
    #   User.order(email: :desc)
376
    #   # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
377
    #
378
    #   User.order(:name, email: :desc)
379
    #   # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
380 381
    #
    #   User.order('name')
382
    #   # SELECT "users".* FROM "users" ORDER BY name
383 384
    #
    #   User.order('name DESC')
385
    #   # SELECT "users".* FROM "users" ORDER BY name DESC
386 387
    #
    #   User.order('name DESC, email')
388
    #   # SELECT "users".* FROM "users" ORDER BY name DESC, email
389
    def order(*args)
390
      check_if_method_has_arguments!(:order, args)
391
      spawn.order!(*args)
392
    end
393

394
    # Same as #order but operates on relation in-place instead of copying.
J
Jon Leighton 已提交
395
    def order!(*args) # :nodoc:
396
      preprocess_order_args(args)
397

398
      self.order_values += args
399
      self
400
    end
401

402 403 404 405 406 407 408 409
    # 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')
    #
410
    # generates a query with 'ORDER BY id ASC, name ASC'.
S
Sebastian Martinez 已提交
411
    def reorder(*args)
412
      check_if_method_has_arguments!(:reorder, args)
413
      spawn.reorder!(*args)
414
    end
415

416
    # Same as #reorder but operates on relation in-place instead of copying.
J
Jon Leighton 已提交
417
    def reorder!(*args) # :nodoc:
418
      preprocess_order_args(args) unless args.all?(&:blank?)
419

420
      self.reordering_value = true
421
      self.order_values = args
422
      self
S
Sebastian Martinez 已提交
423 424
    end

425
    VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
426
                                     :limit, :offset, :joins, :left_outer_joins, :annotate,
427
                                     :includes, :from, :readonly, :having, :optimizer_hints])
428 429 430 431 432

    # Removes an unwanted relation that is already defined on a chain of relations.
    # This is useful when passing around chains of relations and would like to
    # modify the relations without reconstructing the entire chain.
    #
433
    #   User.order('email DESC').unscope(:order) == User.all
434 435 436 437 438
    #
    # The method arguments are symbols which correspond to the names of the methods
    # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
    # The method can also be called with multiple arguments. For example:
    #
439
    #   User.order('email DESC').select('id').where(name: "John")
440 441
    #       .unscope(:order, :select, :where) == User.all
    #
442
    # One can additionally pass a hash as an argument to unscope specific +:where+ values.
443
    # This is done by passing a hash with a single key-value pair. The key should be
444
    # +:where+ and the value should be the where value to unscope. For example:
445
    #
446 447
    #   User.where(name: "John", active: true).unscope(where: :name)
    #       == User.where(active: true)
448
    #
449 450
    # This method is similar to #except, but unlike
    # #except, it persists across merges:
451
    #
J
Jon Leighton 已提交
452 453
    #   User.order('email').merge(User.except(:order))
    #       == User.order('email')
454
    #
J
Jon Leighton 已提交
455 456 457 458 459
    #   User.order('email').merge(User.unscope(:order))
    #       == User.all
    #
    # This means it can be used in association definitions:
    #
R
Ryuta Kamizono 已提交
460
    #   has_many :comments, -> { unscope(where: :trashed) }
461 462
    #
    def unscope(*args)
463
      check_if_method_has_arguments!(:unscope, args)
464 465 466
      spawn.unscope!(*args)
    end

467
    def unscope!(*args) # :nodoc:
468
      args.flatten!
J
Jon Leighton 已提交
469
      self.unscope_values += args
470 471 472 473

      args.each do |scope|
        case scope
        when Symbol
474
          scope = :left_outer_joins if scope == :left_joins
475 476 477
          if !VALID_UNSCOPING_VALUES.include?(scope)
            raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
          end
478
          assert_mutability!
479
          @values.delete(scope)
480 481 482 483 484 485
        when Hash
          scope.each do |key, target_value|
            if key != :where
              raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
            end

486
            target_values = Array(target_value)
487
            self.where_clause = where_clause.except(*target_values)
488 489 490 491 492 493 494 495 496
          end
        else
          raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
        end
      end

      self
    end

497 498
    # Performs a joins on +args+. The given symbol(s) should match the name of
    # the association(s).
499 500
    #
    #   User.joins(:posts)
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
    #   # SELECT "users".*
    #   # FROM "users"
    #   # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
    #
    # Multiple joins:
    #
    #   User.joins(:posts, :account)
    #   # SELECT "users".*
    #   # FROM "users"
    #   # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
    #   # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
    #
    # Nested joins:
    #
    #   User.joins(posts: [:comments])
    #   # SELECT "users".*
    #   # FROM "users"
    #   # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
    #   # INNER JOIN "comments" "comments_posts"
    #   #   ON "comments_posts"."post_id" = "posts"."id"
521 522 523 524
    #
    # You can use strings in order to customize your joins:
    #
    #   User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
525
    #   # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
526
    def joins(*args)
527 528
      check_if_method_has_arguments!(:joins, args)
      spawn.joins!(*args)
529
    end
530

J
Jon Leighton 已提交
531
    def joins!(*args) # :nodoc:
532 533
      args.compact!
      args.flatten!
534
      self.joins_values |= args
535
      self
P
Pratik Naik 已提交
536 537
    end

538 539 540 541 542 543
    # Performs a left outer joins on +args+:
    #
    #   User.left_outer_joins(:posts)
    #   => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
    #
    def left_outer_joins(*args)
544
      check_if_method_has_arguments!(__callee__, args)
545 546
      spawn.left_outer_joins!(*args)
    end
547
    alias :left_joins :left_outer_joins
548 549

    def left_outer_joins!(*args) # :nodoc:
550 551
      args.compact!
      args.flatten!
552
      self.left_outer_joins_values |= args
553 554 555
      self
    end

556 557 558 559 560 561 562 563 564 565
    # 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
566
    # constructor as an SQL fragment, and used in the where clause of the query.
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
    #
    #    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,
599
    # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
    #
    #   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')
    #
631 632 633 634 635 636
    # In the case of a belongs_to relationship, an association key can be used
    # to specify the model if an ActiveRecord object is used as the value.
    #
    #    author = Author.find(1)
    #
    #    # The following queries will be equivalent:
A
AvnerCohen 已提交
637 638
    #    Post.where(author: author)
    #    Post.where(author_id: author)
639 640 641
    #
    # This also works with polymorphic belongs_to relationships:
    #
A
AvnerCohen 已提交
642 643
    #    treasure = Treasure.create(name: 'gold coins')
    #    treasure.price_estimates << PriceEstimate.create(price: 125)
644 645
    #
    #    # The following queries will be equivalent:
A
AvnerCohen 已提交
646 647
    #    PriceEstimate.where(estimate_of: treasure)
    #    PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
648
    #
649 650 651 652 653 654 655 656 657 658
    # === 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 })
A
AvnerCohen 已提交
659
    #    User.joins(:posts).where({ posts: { published: true } })
660
    #
661
    # === no argument
662
    #
663 664
    # If no argument is passed, #where returns a new instance of WhereChain, that
    # can be chained with #not to return a new relation that negates the where clause.
665 666
    #
    #    User.where.not(name: "Jon")
667
    #    # SELECT * FROM users WHERE name != 'Jon'
668
    #
669
    # See WhereChain for more details on #not.
670
    #
671
    # === blank condition
672
    #
673
    # If the condition is any blank-ish object, then #where is a no-op and returns
674
    # the current relation.
675
    def where(opts = :chain, *rest)
676
      if :chain == opts
677 678 679 680 681 682
        WhereChain.new(spawn)
      elsif opts.blank?
        self
      else
        spawn.where!(opts, *rest)
      end
683 684
    end

685
    def where!(opts, *rest) # :nodoc:
686
      self.where_clause += build_where_clause(opts, *rest)
687
      self
688
    end
P
Pratik Naik 已提交
689

690 691
    # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
    #
692 693 694 695 696 697 698 699
    #   Post.where(trashed: true).where(trashed: false)
    #   # WHERE `trashed` = 1 AND `trashed` = 0
    #
    #   Post.where(trashed: true).rewhere(trashed: false)
    #   # WHERE `trashed` = 0
    #
    #   Post.where(active: true).where(trashed: true).rewhere(trashed: false)
    #   # WHERE `active` = 1 AND `trashed` = 0
700
    #
701 702
    # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
    # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
703
    def rewhere(conditions)
704 705 706 707 708 709 710 711 712 713 714
      attrs = []
      scope = spawn

      where_clause = scope.build_where_clause(conditions)
      where_clause.each_attribute do |attr|
        attrs << attr
      end

      scope.unscope!(where: attrs)
      scope.where_clause += where_clause
      scope
715 716
    end

717 718 719 720
    # Returns a new relation, which is the logical union of this relation and the one passed as an
    # argument.
    #
    # The two relations must be structurally compatible: they must be scoping the same model, and
721 722
    # they must differ only by #where (if no #group has been defined) or #having (if a #group is
    # present). Neither relation may have a #limit, #offset, or #distinct set.
723
    #
724
    #    Post.where("id = 1").or(Post.where("author_id = 3"))
R
Ryuta Kamizono 已提交
725
    #    # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
726 727
    #
    def or(other)
728 729 730 731
      unless other.is_a? Relation
        raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
      end

732 733 734
      spawn.or!(other)
    end

735
    def or!(other) # :nodoc:
736 737 738 739
      incompatible_values = structurally_incompatible_values_for_or(other)

      unless incompatible_values.empty?
        raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
740 741
      end

742
      self.where_clause = self.where_clause.or(other.where_clause)
743
      self.having_clause = having_clause.or(other.having_clause)
744
      self.references_values += other.references_values
745 746 747 748

      self
    end

749 750 751 752
    # 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')
753
    def having(opts, *rest)
754
      opts.blank? ? self : spawn.having!(opts, *rest)
755 756
    end

J
Jon Leighton 已提交
757
    def having!(opts, *rest) # :nodoc:
758
      opts = sanitize_forbidden_attributes(opts)
759
      references!(PredicateBuilder.references(opts)) if Hash === opts
760

761
      self.having_clause += having_clause_factory.build(opts, rest)
762
      self
763 764
    end

765
    # Specifies a limit for the number of records to retrieve.
766 767 768 769
    #
    #   User.limit(10) # generated SQL has 'LIMIT 10'
    #
    #   User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
770
    def limit(value)
J
Jon Leighton 已提交
771
      spawn.limit!(value)
772 773
    end

J
Jon Leighton 已提交
774
    def limit!(value) # :nodoc:
775 776
      self.limit_value = value
      self
777 778
    end

779 780 781 782
    # Specifies the number of rows to skip before returning rows.
    #
    #   User.offset(10) # generated SQL has "OFFSET 10"
    #
783
    # Should be used with order.
784
    #
785
    #   User.offset(10).order("name ASC")
786
    def offset(value)
J
Jon Leighton 已提交
787
      spawn.offset!(value)
788 789
    end

J
Jon Leighton 已提交
790
    def offset!(value) # :nodoc:
791 792
      self.offset_value = value
      self
793 794
    end

795
    # Specifies locking settings (default to +true+). For more information
796
    # on locking, please see ActiveRecord::Locking.
797
    def lock(locks = true)
J
Jon Leighton 已提交
798
      spawn.lock!(locks)
799
    end
800

J
Jon Leighton 已提交
801
    def lock!(locks = true) # :nodoc:
802
      case locks
803
      when String, TrueClass, NilClass
804
        self.lock_value = locks || true
805
      else
806
        self.lock_value = false
807
      end
808

809
      self
810 811
    end

812
    # Returns a chainable relation with zero records.
813
    #
814 815 816
    # The returned relation implements the Null Object pattern. It is an
    # object with defined null behavior and always returns an empty array of
    # records without querying the database.
817 818 819 820
    #
    # Any subsequent condition chained to the returned relation will continue
    # generating an empty relation and will not fire any query to the database.
    #
821 822
    # Used in cases where a method or scope could return zero records but the
    # result needs to be chainable.
823 824 825
    #
    # For example:
    #
A
AvnerCohen 已提交
826
    #   @posts = current_user.visible_posts.where(name: params[:name])
827
    #   # the visible_posts method is expected to return a chainable Relation
828 829 830
    #
    #   def visible_posts
    #     case role
831
    #     when 'Country Manager'
A
AvnerCohen 已提交
832
    #       Post.where(country: country)
833
    #     when 'Reviewer'
834
    #       Post.published
835
    #     when 'Bad User'
836
    #       Post.none # It can't be chained if [] is returned.
837 838 839 840
    #     end
    #   end
    #
    def none
841
      spawn.none!
842 843
    end

J
Jon Leighton 已提交
844
    def none! # :nodoc:
845
      where!("1=0").extending!(NullRelation)
846 847
    end

848 849 850 851 852
    # 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
853
    #   => ActiveRecord::ReadOnlyRecord: User is marked as readonly
854
    def readonly(value = true)
J
Jon Leighton 已提交
855
      spawn.readonly!(value)
856 857
    end

J
Jon Leighton 已提交
858
    def readonly!(value = true) # :nodoc:
859 860
      self.readonly_value = value
      self
861 862
    end

863 864 865
    # Sets the returned relation to strict_loading mode. This will raise an error
    # if the record tries to lazily load an association.
    #
866
    #   user = User.strict_loading.first
867 868 869 870 871 872 873 874 875 876 877
    #   user.comments.to_a
    #   => ActiveRecord::StrictLoadingViolationError
    def strict_loading(value = true)
      spawn.strict_loading!(value)
    end

    def strict_loading!(value = true) # :nodoc:
      self.strict_loading_value = value
      self
    end

878 879 880 881 882 883 884 885 886
    # 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'
    #
887
    # You can pass +nil+ to #create_with to reset attributes:
888 889 890
    #
    #   users = users.create_with(nil)
    #   users.new.name # => 'Oscar'
891
    def create_with(value)
J
Jon Leighton 已提交
892
      spawn.create_with!(value)
893 894
    end

J
Jon Leighton 已提交
895
    def create_with!(value) # :nodoc:
896 897 898 899
      if value
        value = sanitize_forbidden_attributes(value)
        self.create_with_value = create_with_value.merge(value)
      else
900
        self.create_with_value = FROZEN_EMPTY_HASH
901 902
      end

903
      self
904 905
    end

906 907 908
    # Specifies table from which the records will be fetched. For example:
    #
    #   Topic.select('title').from('posts')
909
    #   # SELECT title FROM posts
910 911 912
    #
    # Can accept other relation objects. For example:
    #
913
    #   Topic.select('title').from(Topic.approved)
914
    #   # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
915
    #
916
    #   Topic.select('a.title').from(Topic.approved, :a)
917
    #   # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
918 919 920
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
921 922
    end

J
Jon Leighton 已提交
923
    def from!(value, subquery_name = nil) # :nodoc:
924
      self.from_clause = Relation::FromClause.new(value, subquery_name)
925
      self
926 927
    end

928 929 930
    # Specifies whether the records should be unique or not. For example:
    #
    #   User.select(:name)
931
    #   # Might return two records with the same name
932
    #
933
    #   User.select(:name).distinct
934
    #   # Returns 1 record per distinct name
935
    #
936
    #   User.select(:name).distinct.distinct(false)
937
    #   # You can also remove the uniqueness
938 939
    def distinct(value = true)
      spawn.distinct!(value)
940 941
    end

942 943 944
    # Like #distinct, but modifies relation in place.
    def distinct!(value = true) # :nodoc:
      self.distinct_value = value
945
      self
946 947
    end

948
    # Used to extend a scope with additional methods, either through
949 950
    # a module or through a block provided.
    #
951 952 953 954 955 956 957 958 959 960
    # 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
    #
961
    #   scope = Model.all.extending(Pagination)
962 963
    #   scope.page(params[:page])
    #
V
Vijay Dev 已提交
964
    # You can also pass a list of modules:
965
    #
966
    #   scope = Model.all.extending(Pagination, SomethingElse)
967 968 969
    #
    # === Using a block
    #
970
    #   scope = Model.all.extending do
971
    #     def page(number)
972
    #       # pagination code goes here
973 974 975 976 977 978
    #     end
    #   end
    #   scope.page(params[:page])
    #
    # You can also use a block and a module list:
    #
979
    #   scope = Model.all.extending(Pagination) do
980
    #     def per_page(number)
981
    #       # pagination code goes here
982 983
    #     end
    #   end
984 985
    def extending(*modules, &block)
      if modules.any? || block
J
Jon Leighton 已提交
986
        spawn.extending!(*modules, &block)
987 988 989 990
      else
        self
      end
    end
991

J
Jon Leighton 已提交
992
    def extending!(*modules, &block) # :nodoc:
993 994
      modules << Module.new(&block) if block
      modules.flatten!
995

996
      self.extending_values += modules
997
      extend(*extending_values) if extending_values.any?
998

999
      self
1000 1001
    end

R
Ryuta Kamizono 已提交
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
    # Specify optimizer hints to be used in the SELECT statement.
    #
    # Example (for MySQL):
    #
    #   Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
    #   # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
    #
    # Example (for PostgreSQL with pg_hint_plan):
    #
    #   Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
    #   # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
    def optimizer_hints(*args)
      check_if_method_has_arguments!(:optimizer_hints, args)
      spawn.optimizer_hints!(*args)
    end

    def optimizer_hints!(*args) # :nodoc:
      args.flatten!

1021
      self.optimizer_hints_values |= args
R
Ryuta Kamizono 已提交
1022 1023 1024
      self
    end

1025 1026 1027
    # Reverse the existing order clause on the relation.
    #
    #   User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
1028
    def reverse_order
J
Jon Leighton 已提交
1029
      spawn.reverse_order!
1030 1031
    end

J
Jon Leighton 已提交
1032
    def reverse_order! # :nodoc:
1033
      orders = order_values.uniq
1034
      orders.compact_blank!
1035
      self.order_values = reverse_sql_order(orders)
1036
      self
1037 1038
    end

1039 1040
    def skip_query_cache!(value = true) # :nodoc:
      self.skip_query_cache_value = value
1041 1042 1043
      self
    end

1044 1045 1046 1047 1048
    def skip_preloading! # :nodoc:
      self.skip_preloading_value = true
      self
    end

1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
    # Adds an SQL comment to queries generated from this relation. For example:
    #
    #   User.annotate("selecting user names").select(:name)
    #   # SELECT "users"."name" FROM "users" /* selecting user names */
    #
    #   User.annotate("selecting", "user", "names").select(:name)
    #   # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
    #
    # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
    def annotate(*args)
      check_if_method_has_arguments!(:annotate, args)
      spawn.annotate!(*args)
    end

    # Like #annotate, but modifies relation in place.
    def annotate!(*args) # :nodoc:
      self.annotate_values += args
      self
    end

1069
    # Returns the Arel object associated with the relation.
1070 1071
    def arel(aliases = nil) # :nodoc:
      @arel ||= build_arel(aliases)
1072 1073
    end

1074
    def construct_join_dependency(associations, join_type) # :nodoc:
1075
      ActiveRecord::Associations::JoinDependency.new(
1076
        klass, table, associations, join_type
1077 1078 1079
      )
    end

1080 1081 1082 1083 1084 1085 1086 1087 1088
    protected
      def build_subquery(subquery_alias, select_value) # :nodoc:
        subquery = except(:optimizer_hints).arel.as(subquery_alias)

        Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
          arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
        end
      end

1089 1090 1091 1092 1093 1094
      def build_where_clause(opts, *rest)
        opts = sanitize_forbidden_attributes(opts)
        references!(PredicateBuilder.references(opts)) if Hash === opts
        where_clause_factory.build(opts, rest)
      end

1095
    private
1096 1097 1098 1099
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1100

1101
      def build_arel(aliases)
1102
        arel = Arel::SelectManager.new(table)
1103

1104 1105 1106 1107 1108
        if !joins_values.empty?
          build_joins(arel, joins_values.flatten, aliases)
        elsif !left_outer_joins_values.empty?
          build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
        end
1109

1110 1111
        arel.where(where_clause.ast) unless where_clause.empty?
        arel.having(having_clause.ast) unless having_clause.empty?
1112
        if limit_value
1113
          arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value)))
1114 1115
        end
        if offset_value
1116
          arel.skip(build_cast_value("OFFSET", offset_value.to_i))
1117
        end
1118
        arel.group(*arel_columns(group_values.uniq.compact_blank)) unless group_values.empty?
1119

1120
        build_order(arel)
1121

1122
        build_select(arel)
1123

R
Ryuta Kamizono 已提交
1124
        arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1125 1126 1127
        arel.distinct(distinct_value)
        arel.from(build_from) unless from_clause.empty?
        arel.lock(lock_value) if lock_value
1128
        arel.comment(*annotate_values) unless annotate_values.empty?
1129

1130
        arel
1131 1132
      end

1133 1134 1135 1136 1137
      def build_cast_value(name, value)
        cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
        Arel::Nodes::BindParam.new(cast_value)
      end

1138 1139 1140 1141 1142
      def build_from
        opts = from_clause.value
        name = from_clause.name
        case opts
        when Relation
1143 1144 1145
          if opts.eager_loading?
            opts = opts.send(:apply_join_dependency)
          end
1146 1147
          name ||= "subquery"
          opts.arel.as(name.to_s)
1148
        else
1149
          opts
1150 1151 1152
        end
      end

1153 1154
      def select_association_list(associations)
        result = []
1155 1156
        associations.each do |association|
          case association
1157
          when Hash, Symbol, Array
1158
            result << association
1159
          else
1160
            yield if block_given?
1161
          end
A
Aaron Patterson 已提交
1162
        end
1163 1164 1165 1166 1167 1168 1169
        result
      end

      def valid_association_list(associations)
        select_association_list(associations) do
          raise ArgumentError, "only Hash, Symbol and Array are allowed"
        end
1170
      end
1171

1172
      def build_left_outer_joins(manager, outer_joins, aliases)
1173 1174
        buckets = Hash.new { |h, k| h[k] = [] }
        buckets[:association_join] = valid_association_list(outer_joins)
1175
        build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1176
      end
1177

1178
      def build_joins(manager, joins, aliases)
1179 1180
        buckets = Hash.new { |h, k| h[k] = [] }

1181 1182
        unless left_outer_joins_values.empty?
          left_joins = valid_association_list(left_outer_joins_values.flatten)
1183 1184 1185
          buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
        end

1186 1187 1188 1189
        if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
          buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
        end

1190 1191 1192 1193 1194 1195
        joins.map! do |join|
          if join.is_a?(String)
            table.create_string_join(Arel.sql(join.strip)) unless join.blank?
          else
            join
          end
R
Ryuta Kamizono 已提交
1196
        end.compact_blank!.uniq!
1197 1198 1199 1200 1201 1202 1203 1204

        while joins.first.is_a?(Arel::Nodes::Join)
          join_node = joins.shift
          if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
            buckets[:join_node] << join_node
          else
            buckets[:leading_join] << join_node
          end
1205 1206
        end

1207
        joins.each do |join|
1208 1209
          case join
          when Hash, Symbol, Array
1210
            buckets[:association_join] << join
1211
          when ActiveRecord::Associations::JoinDependency
1212
            buckets[:stashed_join] << join
1213
          when Arel::Nodes::Join
1214
            buckets[:join_node] << join
1215 1216 1217 1218
          else
            raise "unknown class: %s" % join.class.name
          end
        end
1219

1220
        build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1221
      end
1222

1223
      def build_join_query(manager, buckets, join_type, aliases)
1224 1225
        association_joins = buckets[:association_join]
        stashed_joins     = buckets[:stashed_join]
R
Ryuta Kamizono 已提交
1226 1227
        leading_joins     = buckets[:leading_join]
        join_nodes        = buckets[:join_node]
1228

1229
        join_sources = manager.join_sources
1230
        join_sources.concat(leading_joins) unless leading_joins.empty?
1231

1232
        unless association_joins.empty? && stashed_joins.empty?
1233
          alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1234 1235 1236
          join_dependency = construct_join_dependency(association_joins, join_type)
          join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
        end
1237

1238
        join_sources.concat(join_nodes) unless join_nodes.empty?
1239 1240
      end

1241 1242 1243
      def build_select(arel)
        if select_values.any?
          arel.project(*arel_columns(select_values.uniq))
1244 1245
        elsif klass.ignored_columns.any?
          arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1246
        else
R
Ryuta Kamizono 已提交
1247
          arel.project(table[Arel.star])
1248 1249 1250
        end
      end

1251
      def arel_columns(columns)
1252
        columns.flat_map do |field|
1253 1254
          case field
          when Symbol
1255 1256 1257
            arel_column(field.to_s) do |attr_name|
              connection.quote_table_name(attr_name)
            end
1258
          when String
1259
            arel_column(field, &:itself)
1260
          when Proc
1261
            field.call
1262 1263 1264 1265
          else
            field
          end
        end
1266
      end
B
Brian Mathiyakom 已提交
1267

1268
      def arel_column(field)
1269
        field = klass.attribute_aliases[field] || field
1270
        from = from_clause.name || from_clause.value
1271

1272
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1273 1274
          arel_attribute(field)
        else
1275
          yield field
1276 1277 1278
        end
      end

1279
      def table_name_matches?(from)
L
Larry Reid 已提交
1280 1281 1282
        table_name = Regexp.escape(table.name)
        quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
        /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1283 1284
      end

1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
      def reverse_sql_order(order_query)
        if order_query.empty?
          return [arel_attribute(primary_key).desc] if primary_key
          raise IrreversibleOrderError,
            "Relation has no current order and table has no primary key to be used as default order"
        end

        order_query.flat_map do |o|
          case o
          when Arel::Attribute
            o.desc
          when Arel::Nodes::Ordering
            o.reverse
          when String
            if does_not_support_reverse?(o)
1300
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1301 1302 1303
            end
            o.split(",").map! do |s|
              s.strip!
1304
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1305 1306 1307
            end
          else
            o
1308
          end
1309
        end
1310
      end
1311

1312
      def does_not_support_reverse?(order)
1313 1314 1315 1316
        # Account for String subclasses like Arel::Nodes::SqlLiteral that
        # override methods like #count.
        order = String.new(order) unless order.instance_of?(String)

1317
        # Uses SQL function with multiple arguments.
R
Rafael Mendonça França 已提交
1318 1319
        (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
          # Uses "nulls first" like construction.
1320
          /\bnulls\s+(?:first|last)\b/i.match?(order)
1321
      end
1322

1323 1324
      def build_order(arel)
        orders = order_values.uniq
1325
        orders.compact_blank!
1326

1327 1328
        arel.order(*orders) unless orders.empty?
      end
1329

1330
      VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1331
                          "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1332

1333 1334 1335 1336
      def validate_order_args(args)
        args.each do |arg|
          next unless arg.is_a?(Hash)
          arg.each do |_key, value|
1337 1338 1339 1340
            unless VALID_DIRECTIONS.include?(value)
              raise ArgumentError,
                "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
            end
1341
          end
1342 1343
        end
      end
1344

1345
      def preprocess_order_args(order_args)
1346
        order_args = sanitize_order_arguments(order_args)
1347
        @klass.disallow_raw_sql!(
1348
          order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1349
          permit: connection.column_name_with_order_matcher
1350 1351
        )

1352 1353
        validate_order_args(order_args)

1354
        references = column_references(order_args)
1355 1356 1357 1358 1359 1360
        references!(references) if references.any?

        # if a symbol is given we prepend the quoted table name
        order_args.map! do |arg|
          case arg
          when Symbol
1361
            order_column(arg.to_s).asc
1362 1363
          when Hash
            arg.map { |field, dir|
1364 1365 1366 1367
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
1368
                order_column(field.to_s).send(dir.downcase)
1369
              end
1370 1371 1372 1373 1374 1375
            }
          else
            arg
          end
        end.flatten!
      end
1376

1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391
      def sanitize_order_arguments(order_args)
        order_args.reject!(&:blank?)
        order_args.map! do |arg|
          klass.sanitize_sql_for_order(arg)
        end
        order_args.flatten!
        order_args
      end

      def column_references(order_args)
        references = order_args.grep(String)
        references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
        references
      end

1392 1393 1394 1395 1396 1397 1398 1399 1400 1401
      def order_column(field)
        arel_column(field) do |attr_name|
          if attr_name == "count" && !group_values.empty?
            arel_attribute(attr_name)
          else
            Arel.sql(connection.quote_table_name(attr_name))
          end
        end
      end

1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417
      # Checks to make sure that the arguments are not blank. Note that if some
      # blank-like object were initially passed into the query method, then this
      # method will not raise an error.
      #
      # Example:
      #
      #    Post.references()   # raises an error
      #    Post.references([]) # does not raise an error
      #
      # This particular method should be called with a method_name and the args
      # passed into that method as an input. For example:
      #
      # def references(*args)
      #   check_if_method_has_arguments!("references", args)
      #   ...
      # end
1418 1419 1420 1421
      def check_if_method_has_arguments!(method_name, args)
        if args.blank?
          raise ArgumentError, "The method .#{method_name}() must contain arguments."
        end
1422
      end
S
Sean Griffin 已提交
1423

1424
      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1425
      def structurally_incompatible_values_for_or(other)
1426
        values = other.values
1427
        STRUCTURAL_OR_METHODS.reject do |method|
1428 1429
          v1, v2 = @values[method], values[method]
          v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
1430
        end
1431
      end
1432

1433 1434 1435 1436
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1437 1438
  end
end