query_methods.rb 48.8 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
require "active_support/core_ext/array/wrap"
9

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

14 15
    include ActiveModel::ForbiddenAttributesProtection

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

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

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

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

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

        if not_behaves_as_nor?(opts)
          ActiveSupport::Deprecation.warn(<<~MSG.squish)
            NOT conditions will no longer behave as NOR in Rails 6.1.
55
            To continue using NOR conditions, NOT each condition individually
56 57 58 59 60 61 62 63 64
            (`#{
              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
            }`).
65 66 67 68 69 70
          MSG
          @scope.where_clause += where_clause.invert(:nor)
        else
          @scope.where_clause += where_clause.invert
        end

71 72
        @scope
      end
73

G
Godfrey Chan 已提交
74
      # Returns a new relation with left outer joins and where clause to identify
75 76 77 78 79
      # missing relations.
      #
      # For example, posts that are missing a related author:
      #
      #    Post.where.missing(:author)
80 81 82
      #    # SELECT "posts".* FROM "posts"
      #    # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
      #    # WHERE "authors"."id" IS NULL
83
      #
84
      # Additionally, multiple relations can be combined. This will return posts
85
      # that are missing both an author and any comments:
86 87
      #
      #    Post.where.missing(:author, :comments)
88 89 90 91
      #    # 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
92 93 94 95 96 97 98 99 100 101 102
      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

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

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

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

115
    Relation::VALUE_METHODS.each do |name|
R
Ryuta Kamizono 已提交
116
      method_name, default =
117
        case name
R
Ryuta Kamizono 已提交
118 119 120 121 122
        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
123
          ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
124
        end
R
Ryuta Kamizono 已提交
125

126
      class_eval <<-CODE, __FILE__, __LINE__ + 1
R
Ryuta Kamizono 已提交
127 128 129 130 131 132 133 134
        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
135
      CODE
136 137
    end

138
    alias extensions extending_values
139

O
Oscar Del Ben 已提交
140 141 142 143 144 145 146 147 148 149
    # 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
150
    # performance improvement over a simple join.
151
    #
152 153 154 155 156 157 158 159
    # 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])
    #
160 161
    # === conditions
    #
162
    # If you want to add string conditions to your included models, you'll have
163 164 165 166 167 168 169
    # 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)
170
    #
171
    # Note that #includes works with association names while #references needs
172
    # the actual table name.
173 174 175 176 177
    #
    # 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:
    #
178
    #   User.includes(:posts).where(posts: { name: 'example' })
179
    def includes(*args)
180
      check_if_method_has_arguments!(:includes, args)
181
      spawn.includes!(*args)
182
    end
183

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

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

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

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

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

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

222 223 224 225 226 227 228 229 230 231 232 233 234
    # 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

235 236
    # 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.
237
    # This method only works in conjunction with #includes.
238
    # See #includes for more details.
239 240
    #
    #   User.includes(:posts).where("posts.name = 'foo'")
241
    #   # Doesn't JOIN the posts table, resulting in an error.
242 243
    #
    #   User.includes(:posts).where("posts.name = 'foo'").references(:posts)
244
    #   # Query now knows the string references posts, so adds a JOIN
245 246 247
    def references(*table_names)
      check_if_method_has_arguments!(:references, table_names)
      spawn.references!(*table_names)
248
    end
249

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

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

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

        return super()
      end

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

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

W
Willian Gustavo Veiga 已提交
318 319 320
    # Allows you to change a previously set select statement.
    #
    #   Post.select(:title, :body)
321
    #   # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
W
Willian Gustavo Veiga 已提交
322 323
    #
    #   Post.select(:title, :body).reselect(:created_at)
324
    #   # SELECT `posts`.`created_at` FROM `posts`
W
Willian Gustavo Veiga 已提交
325 326 327
    #
    # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
    # Note that we're unscoping the entire select statement.
328 329 330 331 332 333 334 335 336
    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 已提交
337
    end
S
Santiago Pastorino 已提交
338

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

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

367
      self.group_values += args
368
      self
369
    end
370

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

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

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

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

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

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

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

    # 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.
    #
434
    #   User.order('email DESC').unscope(:order) == User.all
435 436 437 438 439
    #
    # 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:
    #
440
    #   User.order('email DESC').select('id').where(name: "John")
441 442
    #       .unscope(:order, :select, :where) == User.all
    #
443
    # One can additionally pass a hash as an argument to unscope specific +:where+ values.
444
    # This is done by passing a hash with a single key-value pair. The key should be
445
    # +:where+ and the value should be the where value to unscope. For example:
446
    #
447 448
    #   User.where(name: "John", active: true).unscope(where: :name)
    #       == User.where(active: true)
449
    #
450 451
    # This method is similar to #except, but unlike
    # #except, it persists across merges:
452
    #
J
Jon Leighton 已提交
453 454
    #   User.order('email').merge(User.except(:order))
    #       == User.order('email')
455
    #
J
Jon Leighton 已提交
456 457 458 459 460
    #   User.order('email').merge(User.unscope(:order))
    #       == User.all
    #
    # This means it can be used in association definitions:
    #
R
Ryuta Kamizono 已提交
461
    #   has_many :comments, -> { unscope(where: :trashed) }
462 463
    #
    def unscope(*args)
464
      check_if_method_has_arguments!(:unscope, args)
465 466 467
      spawn.unscope!(*args)
    end

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

      args.each do |scope|
        case scope
        when Symbol
475
          scope = :left_outer_joins if scope == :left_joins
476 477 478
          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
479
          assert_mutability!
480
          @values.delete(scope)
481 482 483 484 485 486
        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

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

      self
    end

498 499
    # Performs a joins on +args+. The given symbol(s) should match the name of
    # the association(s).
500 501
    #
    #   User.joins(:posts)
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
    #   # 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"
522 523 524 525
    #
    # 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")
526
    #   # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
527
    def joins(*args)
528 529
      check_if_method_has_arguments!(:joins, args)
      spawn.joins!(*args)
530
    end
531

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

539 540 541 542 543 544
    # 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)
545
      check_if_method_has_arguments!(__callee__, args)
546 547
      spawn.left_outer_joins!(*args)
    end
548
    alias :left_joins :left_outer_joins
549 550

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

557 558 559 560 561 562 563 564 565 566
    # 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
567
    # constructor as an SQL fragment, and used in the where clause of the query.
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 599
    #
    #    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,
600
    # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
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 631
    #
    #   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')
    #
632 633 634 635 636 637
    # 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 已提交
638 639
    #    Post.where(author: author)
    #    Post.where(author_id: author)
640 641 642
    #
    # This also works with polymorphic belongs_to relationships:
    #
A
AvnerCohen 已提交
643 644
    #    treasure = Treasure.create(name: 'gold coins')
    #    treasure.price_estimates << PriceEstimate.create(price: 125)
645 646
    #
    #    # The following queries will be equivalent:
A
AvnerCohen 已提交
647 648
    #    PriceEstimate.where(estimate_of: treasure)
    #    PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
649
    #
650 651 652 653 654 655 656 657 658 659
    # === 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 已提交
660
    #    User.joins(:posts).where({ posts: { published: true } })
661
    #
662
    # === no argument
663
    #
664 665
    # 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.
666 667
    #
    #    User.where.not(name: "Jon")
668
    #    # SELECT * FROM users WHERE name != 'Jon'
669
    #
670
    # See WhereChain for more details on #not.
671
    #
672
    # === blank condition
673
    #
674
    # If the condition is any blank-ish object, then #where is a no-op and returns
675
    # the current relation.
676
    def where(opts = :chain, *rest)
677
      if :chain == opts
678 679 680 681 682 683
        WhereChain.new(spawn)
      elsif opts.blank?
        self
      else
        spawn.where!(opts, *rest)
      end
684 685
    end

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

691 692
    # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
    #
693 694 695 696 697 698 699 700
    #   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
701
    #
702 703
    # 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.
704
    def rewhere(conditions)
705 706 707 708 709 710 711 712 713 714 715
      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
716 717
    end

718 719 720 721
    # 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
722 723
    # 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.
724
    #
725
    #    Post.where("id = 1").or(Post.where("author_id = 3"))
R
Ryuta Kamizono 已提交
726
    #    # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
727 728
    #
    def or(other)
729 730 731 732
      unless other.is_a? Relation
        raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
      end

733 734 735
      spawn.or!(other)
    end

736
    def or!(other) # :nodoc:
737 738 739 740
      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}"
741 742
      end

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

      self
    end

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

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

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

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

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

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

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

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

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

810
      self
811 812
    end

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

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

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

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

864 865 866
    # Sets the returned relation to strict_loading mode. This will raise an error
    # if the record tries to lazily load an association.
    #
867
    #   user = User.strict_loading.first
868 869 870 871 872 873 874 875 876 877 878
    #   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

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

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

904
      self
905 906
    end

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

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

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

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

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

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

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

1000
      self
1001 1002
    end

R
Ryuta Kamizono 已提交
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
    # 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!

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

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

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

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

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

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
    # 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

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

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

1081 1082 1083 1084 1085 1086 1087 1088 1089
    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

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

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

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

1105 1106 1107 1108 1109
        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
1110

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

1121
        build_order(arel)
1122

1123
        build_select(arel)
1124

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

1131
        arel
1132 1133
      end

1134 1135 1136 1137 1138
      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

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

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

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

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

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

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

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

1191 1192 1193 1194 1195 1196
        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 已提交
1197
        end.compact_blank!.uniq!
1198 1199 1200 1201 1202 1203 1204 1205

        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
1206 1207
        end

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

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

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

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

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

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

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

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

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

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

1280
      def table_name_matches?(from)
L
Larry Reid 已提交
1281 1282 1283
        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)
1284 1285
      end

1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
      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)
1301
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1302 1303 1304
            end
            o.split(",").map! do |s|
              s.strip!
1305
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1306 1307 1308
            end
          else
            o
1309
          end
1310
        end
1311
      end
1312

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

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

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

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

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

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

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

1353 1354
        validate_order_args(order_args)

1355
        references = column_references(order_args)
1356 1357 1358 1359 1360 1361
        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
1362
            order_column(arg.to_s).asc
1363 1364
          when Hash
            arg.map { |field, dir|
1365 1366 1367 1368
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
1369
                order_column(field.to_s).send(dir.downcase)
1370
              end
1371 1372 1373 1374 1375 1376
            }
          else
            arg
          end
        end.flatten!
      end
1377

1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392
      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

1393 1394 1395 1396 1397 1398 1399 1400 1401 1402
      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

1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
      def resolve_arel_attributes(attrs)
        attrs.flat_map do |attr|
          case attr
          when Arel::Attributes::Attribute
            attr
          when Hash
            attr.flat_map do |table, columns|
              table = table.to_s
              Array(columns).map do |column|
                predicate_builder.resolve_arel_attribute(table, column)
              end
            end
          else
            attr = attr.to_s
            if attr.include?(".")
              table, column = attr.split(".", 2)
              predicate_builder.resolve_arel_attribute(table, column)
            else
              attr
            end
          end
        end
      end

1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442
      # 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
1443 1444 1445 1446
      def check_if_method_has_arguments!(method_name, args)
        if args.blank?
          raise ArgumentError, "The method .#{method_name}() must contain arguments."
        end
1447
      end
S
Sean Griffin 已提交
1448

1449
      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1450
      def structurally_incompatible_values_for_or(other)
1451
        values = other.values
1452
        STRUCTURAL_OR_METHODS.reject do |method|
1453
          v1, v2 = @values[method], values[method]
1454 1455
          v1 = v1.uniq if v1.is_a?(Array)
          v2 = v2.uniq if v2.is_a?(Array)
1456
          v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
1457
        end
1458
      end
1459

1460 1461 1462 1463
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1464 1465
  end
end