query_methods.rb 47.5 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

73 74 75 76 77 78
      # Returns a new relation with left outer joins and where clause to idenitfy
      # 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 487
            target_values = Array(target_value).map(&:to_s)
            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 687
      opts = sanitize_forbidden_attributes(opts)
      references!(PredicateBuilder.references(opts)) if Hash === opts
688
      self.where_clause += where_clause_factory.build(opts, rest)
689
      self
690
    end
P
Pratik Naik 已提交
691

692 693
    # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
    #
694 695 696 697 698 699 700 701
    #   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
702
    #
703 704
    # 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.
705 706 707 708
    def rewhere(conditions)
      unscope(where: conditions.keys).where(conditions)
    end

709 710 711 712
    # 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
713 714
    # 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.
715
    #
716
    #    Post.where("id = 1").or(Post.where("author_id = 3"))
R
Ryuta Kamizono 已提交
717
    #    # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
718 719
    #
    def or(other)
720 721 722 723
      unless other.is_a? Relation
        raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
      end

724 725 726
      spawn.or!(other)
    end

727
    def or!(other) # :nodoc:
728 729 730 731
      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}"
732 733
      end

734
      self.where_clause = self.where_clause.or(other.where_clause)
735
      self.having_clause = having_clause.or(other.having_clause)
736
      self.references_values += other.references_values
737 738 739 740

      self
    end

741 742 743 744
    # 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')
745
    def having(opts, *rest)
746
      opts.blank? ? self : spawn.having!(opts, *rest)
747 748
    end

J
Jon Leighton 已提交
749
    def having!(opts, *rest) # :nodoc:
750
      opts = sanitize_forbidden_attributes(opts)
751
      references!(PredicateBuilder.references(opts)) if Hash === opts
752

753
      self.having_clause += having_clause_factory.build(opts, rest)
754
      self
755 756
    end

757
    # Specifies a limit for the number of records to retrieve.
758 759 760 761
    #
    #   User.limit(10) # generated SQL has 'LIMIT 10'
    #
    #   User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
762
    def limit(value)
J
Jon Leighton 已提交
763
      spawn.limit!(value)
764 765
    end

J
Jon Leighton 已提交
766
    def limit!(value) # :nodoc:
767 768
      self.limit_value = value
      self
769 770
    end

771 772 773 774
    # Specifies the number of rows to skip before returning rows.
    #
    #   User.offset(10) # generated SQL has "OFFSET 10"
    #
775
    # Should be used with order.
776
    #
777
    #   User.offset(10).order("name ASC")
778
    def offset(value)
J
Jon Leighton 已提交
779
      spawn.offset!(value)
780 781
    end

J
Jon Leighton 已提交
782
    def offset!(value) # :nodoc:
783 784
      self.offset_value = value
      self
785 786
    end

787
    # Specifies locking settings (default to +true+). For more information
788
    # on locking, please see ActiveRecord::Locking.
789
    def lock(locks = true)
J
Jon Leighton 已提交
790
      spawn.lock!(locks)
791
    end
792

J
Jon Leighton 已提交
793
    def lock!(locks = true) # :nodoc:
794
      case locks
795
      when String, TrueClass, NilClass
796
        self.lock_value = locks || true
797
      else
798
        self.lock_value = false
799
      end
800

801
      self
802 803
    end

804
    # Returns a chainable relation with zero records.
805
    #
806 807 808
    # 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.
809 810 811 812
    #
    # Any subsequent condition chained to the returned relation will continue
    # generating an empty relation and will not fire any query to the database.
    #
813 814
    # Used in cases where a method or scope could return zero records but the
    # result needs to be chainable.
815 816 817
    #
    # For example:
    #
A
AvnerCohen 已提交
818
    #   @posts = current_user.visible_posts.where(name: params[:name])
819
    #   # the visible_posts method is expected to return a chainable Relation
820 821 822
    #
    #   def visible_posts
    #     case role
823
    #     when 'Country Manager'
A
AvnerCohen 已提交
824
    #       Post.where(country: country)
825
    #     when 'Reviewer'
826
    #       Post.published
827
    #     when 'Bad User'
828
    #       Post.none # It can't be chained if [] is returned.
829 830 831 832
    #     end
    #   end
    #
    def none
833
      spawn.none!
834 835
    end

J
Jon Leighton 已提交
836
    def none! # :nodoc:
837
      where!("1=0").extending!(NullRelation)
838 839
    end

840 841 842 843 844
    # 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
845
    #   => ActiveRecord::ReadOnlyRecord: User is marked as readonly
846
    def readonly(value = true)
J
Jon Leighton 已提交
847
      spawn.readonly!(value)
848 849
    end

J
Jon Leighton 已提交
850
    def readonly!(value = true) # :nodoc:
851 852
      self.readonly_value = value
      self
853 854
    end

855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
    # Sets the returned relation to strict_loading mode. This will raise an error
    # if the record tries to lazily load an association.
    #
    #   user = User.first.strict_loading
    #   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

870 871 872 873 874 875 876 877 878
    # 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'
    #
879
    # You can pass +nil+ to #create_with to reset attributes:
880 881 882
    #
    #   users = users.create_with(nil)
    #   users.new.name # => 'Oscar'
883
    def create_with(value)
J
Jon Leighton 已提交
884
      spawn.create_with!(value)
885 886
    end

J
Jon Leighton 已提交
887
    def create_with!(value) # :nodoc:
888 889 890 891
      if value
        value = sanitize_forbidden_attributes(value)
        self.create_with_value = create_with_value.merge(value)
      else
892
        self.create_with_value = FROZEN_EMPTY_HASH
893 894
      end

895
      self
896 897
    end

898 899 900
    # Specifies table from which the records will be fetched. For example:
    #
    #   Topic.select('title').from('posts')
901
    #   # SELECT title FROM posts
902 903 904
    #
    # Can accept other relation objects. For example:
    #
905
    #   Topic.select('title').from(Topic.approved)
906
    #   # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
907
    #
908
    #   Topic.select('a.title').from(Topic.approved, :a)
909
    #   # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
910 911 912
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
913 914
    end

J
Jon Leighton 已提交
915
    def from!(value, subquery_name = nil) # :nodoc:
916
      self.from_clause = Relation::FromClause.new(value, subquery_name)
917
      self
918 919
    end

920 921 922
    # Specifies whether the records should be unique or not. For example:
    #
    #   User.select(:name)
923
    #   # Might return two records with the same name
924
    #
925
    #   User.select(:name).distinct
926
    #   # Returns 1 record per distinct name
927
    #
928
    #   User.select(:name).distinct.distinct(false)
929
    #   # You can also remove the uniqueness
930 931
    def distinct(value = true)
      spawn.distinct!(value)
932 933
    end

934 935 936
    # Like #distinct, but modifies relation in place.
    def distinct!(value = true) # :nodoc:
      self.distinct_value = value
937
      self
938 939
    end

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

J
Jon Leighton 已提交
984
    def extending!(*modules, &block) # :nodoc:
985 986
      modules << Module.new(&block) if block
      modules.flatten!
987

988
      self.extending_values += modules
989
      extend(*extending_values) if extending_values.any?
990

991
      self
992 993
    end

R
Ryuta Kamizono 已提交
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    # 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!

1013
      self.optimizer_hints_values |= args
R
Ryuta Kamizono 已提交
1014 1015 1016
      self
    end

1017 1018 1019
    # Reverse the existing order clause on the relation.
    #
    #   User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
1020
    def reverse_order
J
Jon Leighton 已提交
1021
      spawn.reverse_order!
1022 1023
    end

J
Jon Leighton 已提交
1024
    def reverse_order! # :nodoc:
1025
      orders = order_values.uniq
1026
      orders.compact_blank!
1027
      self.order_values = reverse_sql_order(orders)
1028
      self
1029 1030
    end

1031 1032
    def skip_query_cache!(value = true) # :nodoc:
      self.skip_query_cache_value = value
1033 1034 1035
      self
    end

1036 1037 1038 1039 1040
    def skip_preloading! # :nodoc:
      self.skip_preloading_value = true
      self
    end

1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    # 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

1061
    # Returns the Arel object associated with the relation.
1062 1063
    def arel(aliases = nil) # :nodoc:
      @arel ||= build_arel(aliases)
1064 1065
    end

1066
    def construct_join_dependency(associations, join_type) # :nodoc:
1067
      ActiveRecord::Associations::JoinDependency.new(
1068
        klass, table, associations, join_type
1069 1070 1071
      )
    end

1072 1073 1074 1075 1076 1077 1078 1079 1080
    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

1081
    private
1082 1083 1084 1085
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1086

1087
      def build_arel(aliases)
1088
        arel = Arel::SelectManager.new(table)
1089

1090 1091 1092 1093 1094
        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
1095

1096 1097
        arel.where(where_clause.ast) unless where_clause.empty?
        arel.having(having_clause.ast) unless having_clause.empty?
1098
        if limit_value
1099
          limit_attribute = ActiveModel::Attribute.with_cast_value(
1100
            "LIMIT",
1101 1102 1103 1104 1105 1106
            connection.sanitize_limit(limit_value),
            Type.default_value,
          )
          arel.take(Arel::Nodes::BindParam.new(limit_attribute))
        end
        if offset_value
1107
          offset_attribute = ActiveModel::Attribute.with_cast_value(
1108
            "OFFSET",
1109 1110 1111 1112 1113
            offset_value.to_i,
            Type.default_value,
          )
          arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
        end
1114
        arel.group(*arel_columns(group_values.uniq.compact_blank)) unless group_values.empty?
1115

1116
        build_order(arel)
1117

1118
        build_select(arel)
1119

R
Ryuta Kamizono 已提交
1120
        arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1121 1122 1123
        arel.distinct(distinct_value)
        arel.from(build_from) unless from_clause.empty?
        arel.lock(lock_value) if lock_value
1124
        arel.comment(*annotate_values) unless annotate_values.empty?
1125

1126
        arel
1127 1128
      end

1129 1130 1131 1132 1133
      def build_from
        opts = from_clause.value
        name = from_clause.name
        case opts
        when Relation
1134 1135 1136
          if opts.eager_loading?
            opts = opts.send(:apply_join_dependency)
          end
1137 1138
          name ||= "subquery"
          opts.arel.as(name.to_s)
1139
        else
1140
          opts
1141 1142 1143
        end
      end

1144 1145
      def select_association_list(associations)
        result = []
1146 1147
        associations.each do |association|
          case association
1148
          when Hash, Symbol, Array
1149
            result << association
1150
          else
1151
            yield if block_given?
1152
          end
A
Aaron Patterson 已提交
1153
        end
1154 1155 1156 1157 1158 1159 1160
        result
      end

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

1163
      def build_left_outer_joins(manager, outer_joins, aliases)
1164 1165
        buckets = Hash.new { |h, k| h[k] = [] }
        buckets[:association_join] = valid_association_list(outer_joins)
1166
        build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1167
      end
1168

1169
      def build_joins(manager, joins, aliases)
1170 1171
        buckets = Hash.new { |h, k| h[k] = [] }

1172 1173
        unless left_outer_joins_values.empty?
          left_joins = valid_association_list(left_outer_joins_values.flatten)
1174 1175 1176
          buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
        end

1177 1178 1179 1180
        if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
          buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
        end

1181 1182 1183 1184 1185 1186
        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 已提交
1187
        end.compact_blank!.uniq!
1188 1189 1190 1191 1192 1193 1194 1195

        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
1196 1197
        end

1198
        joins.each do |join|
1199 1200
          case join
          when Hash, Symbol, Array
1201
            buckets[:association_join] << join
1202
          when ActiveRecord::Associations::JoinDependency
1203
            buckets[:stashed_join] << join
1204
          when Arel::Nodes::Join
1205
            buckets[:join_node] << join
1206 1207 1208 1209
          else
            raise "unknown class: %s" % join.class.name
          end
        end
1210

1211
        build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1212
      end
1213

1214
      def build_join_query(manager, buckets, join_type, aliases)
1215 1216
        association_joins = buckets[:association_join]
        stashed_joins     = buckets[:stashed_join]
R
Ryuta Kamizono 已提交
1217 1218
        leading_joins     = buckets[:leading_join]
        join_nodes        = buckets[:join_node]
1219

1220
        join_sources = manager.join_sources
1221
        join_sources.concat(leading_joins) unless leading_joins.empty?
1222

1223
        unless association_joins.empty? && stashed_joins.empty?
1224
          alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1225 1226 1227
          join_dependency = construct_join_dependency(association_joins, join_type)
          join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
        end
1228

1229
        join_sources.concat(join_nodes) unless join_nodes.empty?
1230 1231
      end

1232 1233 1234
      def build_select(arel)
        if select_values.any?
          arel.project(*arel_columns(select_values.uniq))
1235 1236
        elsif klass.ignored_columns.any?
          arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1237
        else
R
Ryuta Kamizono 已提交
1238
          arel.project(table[Arel.star])
1239 1240 1241
        end
      end

1242
      def arel_columns(columns)
1243
        columns.flat_map do |field|
1244 1245
          case field
          when Symbol
1246 1247 1248
            arel_column(field.to_s) do |attr_name|
              connection.quote_table_name(attr_name)
            end
1249
          when String
1250
            arel_column(field, &:itself)
1251
          when Proc
1252
            field.call
1253 1254 1255 1256
          else
            field
          end
        end
1257
      end
B
Brian Mathiyakom 已提交
1258

1259
      def arel_column(field)
1260
        field = klass.attribute_aliases[field] || field
1261
        from = from_clause.name || from_clause.value
1262

1263
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1264 1265
          arel_attribute(field)
        else
1266
          yield field
1267 1268 1269
        end
      end

1270
      def table_name_matches?(from)
L
Larry Reid 已提交
1271 1272 1273
        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)
1274 1275
      end

1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290
      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)
1291
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1292 1293 1294
            end
            o.split(",").map! do |s|
              s.strip!
1295
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1296 1297 1298
            end
          else
            o
1299
          end
1300
        end
1301
      end
1302

1303
      def does_not_support_reverse?(order)
1304 1305 1306 1307
        # Account for String subclasses like Arel::Nodes::SqlLiteral that
        # override methods like #count.
        order = String.new(order) unless order.instance_of?(String)

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

1314 1315
      def build_order(arel)
        orders = order_values.uniq
1316
        orders.compact_blank!
1317

1318 1319
        arel.order(*orders) unless orders.empty?
      end
1320

1321
      VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1322
                          "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1323

1324 1325 1326 1327
      def validate_order_args(args)
        args.each do |arg|
          next unless arg.is_a?(Hash)
          arg.each do |_key, value|
1328 1329 1330 1331
            unless VALID_DIRECTIONS.include?(value)
              raise ArgumentError,
                "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
            end
1332
          end
1333 1334
        end
      end
1335

1336
      def preprocess_order_args(order_args)
1337
        order_args.reject!(&:blank?)
1338
        order_args.map! do |arg|
1339
          klass.sanitize_sql_for_order(arg)
1340
        end
1341
        order_args.flatten!
1342

1343
        @klass.disallow_raw_sql!(
1344
          order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1345
          permit: connection.column_name_with_order_matcher
1346 1347
        )

1348 1349 1350
        validate_order_args(order_args)

        references = order_args.grep(String)
1351
        references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1352 1353 1354 1355 1356 1357
        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
1358
            order_column(arg.to_s).asc
1359 1360
          when Hash
            arg.map { |field, dir|
1361 1362 1363 1364
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
1365
                order_column(field.to_s).send(dir.downcase)
1366
              end
1367 1368 1369 1370 1371 1372
            }
          else
            arg
          end
        end.flatten!
      end
1373

1374 1375 1376 1377 1378 1379 1380 1381 1382 1383
      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

1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399
      # 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
1400 1401 1402 1403
      def check_if_method_has_arguments!(method_name, args)
        if args.blank?
          raise ArgumentError, "The method .#{method_name}() must contain arguments."
        end
1404
      end
S
Sean Griffin 已提交
1405

1406
      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1407
      def structurally_incompatible_values_for_or(other)
1408
        values = other.values
1409
        STRUCTURAL_OR_METHODS.reject do |method|
1410 1411
          v1, v2 = @values[method], values[method]
          v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
1412
        end
1413
      end
1414

1415 1416 1417 1418
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1419 1420
  end
end