query_methods.rb 49.2 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
      scope = spawn
      where_clause = scope.build_where_clause(conditions)

708
      scope.unscope!(where: where_clause.extract_attributes)
709 710
      scope.where_clause += where_clause
      scope
711 712
    end

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

728 729 730
      spawn.or!(other)
    end

731
    def or!(other) # :nodoc:
732 733 734 735
      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}"
736 737
      end

738
      self.where_clause = self.where_clause.or(other.where_clause)
739
      self.having_clause = having_clause.or(other.having_clause)
740
      self.references_values += other.references_values
741 742 743 744

      self
    end

745 746 747 748
    # 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')
749
    def having(opts, *rest)
750
      opts.blank? ? self : spawn.having!(opts, *rest)
751 752
    end

J
Jon Leighton 已提交
753
    def having!(opts, *rest) # :nodoc:
754
      opts = sanitize_forbidden_attributes(opts)
755
      references!(PredicateBuilder.references(opts)) if Hash === opts
756

757
      self.having_clause += having_clause_factory.build(opts, rest)
758
      self
759 760
    end

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

J
Jon Leighton 已提交
770
    def limit!(value) # :nodoc:
771 772
      self.limit_value = value
      self
773 774
    end

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

J
Jon Leighton 已提交
786
    def offset!(value) # :nodoc:
787 788
      self.offset_value = value
      self
789 790
    end

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

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

805
      self
806 807
    end

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

J
Jon Leighton 已提交
840
    def none! # :nodoc:
841
      where!("1=0").extending!(NullRelation)
842 843
    end

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

J
Jon Leighton 已提交
854
    def readonly!(value = true) # :nodoc:
855 856
      self.readonly_value = value
      self
857 858
    end

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

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

J
Jon Leighton 已提交
891
    def create_with!(value) # :nodoc:
892 893 894 895
      if value
        value = sanitize_forbidden_attributes(value)
        self.create_with_value = create_with_value.merge(value)
      else
896
        self.create_with_value = FROZEN_EMPTY_HASH
897 898
      end

899
      self
900 901
    end

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

J
Jon Leighton 已提交
919
    def from!(value, subquery_name = nil) # :nodoc:
920
      self.from_clause = Relation::FromClause.new(value, subquery_name)
921
      self
922 923
    end

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

938 939 940
    # Like #distinct, but modifies relation in place.
    def distinct!(value = true) # :nodoc:
      self.distinct_value = value
941
      self
942 943
    end

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

J
Jon Leighton 已提交
988
    def extending!(*modules, &block) # :nodoc:
989 990
      modules << Module.new(&block) if block
      modules.flatten!
991

992
      self.extending_values += modules
993
      extend(*extending_values) if extending_values.any?
994

995
      self
996 997
    end

R
Ryuta Kamizono 已提交
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
    # 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!

1017
      self.optimizer_hints_values |= args
R
Ryuta Kamizono 已提交
1018 1019 1020
      self
    end

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

J
Jon Leighton 已提交
1028
    def reverse_order! # :nodoc:
1029
      orders = order_values.uniq
1030
      orders.compact_blank!
1031
      self.order_values = reverse_sql_order(orders)
1032
      self
1033 1034
    end

1035 1036
    def skip_query_cache!(value = true) # :nodoc:
      self.skip_query_cache_value = value
1037 1038 1039
      self
    end

1040 1041 1042 1043 1044
    def skip_preloading! # :nodoc:
      self.skip_preloading_value = true
      self
    end

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
    # 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

1065
    # Returns the Arel object associated with the relation.
1066 1067
    def arel(aliases = nil) # :nodoc:
      @arel ||= build_arel(aliases)
1068 1069
    end

1070
    def construct_join_dependency(associations, join_type) # :nodoc:
1071
      ActiveRecord::Associations::JoinDependency.new(
1072
        klass, table, associations, join_type
1073 1074 1075
      )
    end

1076 1077 1078 1079 1080 1081 1082 1083 1084
    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

1085 1086 1087 1088 1089 1090
      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

1091
    private
1092 1093 1094 1095
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1096

1097
      def build_arel(aliases)
1098
        arel = Arel::SelectManager.new(table)
1099

1100 1101 1102 1103 1104
        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
1105

1106 1107
        arel.where(where_clause.ast) unless where_clause.empty?
        arel.having(having_clause.ast) unless having_clause.empty?
1108
        if limit_value
1109
          arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value)))
1110 1111
        end
        if offset_value
1112
          arel.skip(build_cast_value("OFFSET", offset_value.to_i))
1113
        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_cast_value(name, value)
        cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
        Arel::Nodes::BindParam.new(cast_value)
      end

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

1149
      def select_association_list(associations, stashed_joins = nil)
1150
        result = []
1151 1152
        associations.each do |association|
          case association
1153
          when Hash, Symbol, Array
1154
            result << association
1155 1156
          when ActiveRecord::Associations::JoinDependency
            stashed_joins&.<< association
1157
          else
1158
            yield association if block_given?
1159
          end
A
Aaron Patterson 已提交
1160
        end
1161 1162 1163
        result
      end

1164 1165
      def valid_association_list(associations, stashed_joins)
        select_association_list(associations, stashed_joins) do
1166 1167
          raise ArgumentError, "only Hash, Symbol and Array are allowed"
        end
1168
      end
1169

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

1176 1177 1178
      class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
      end

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

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

1188
        if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1189
          stashed_eager_load = joins.pop if joins.last.base_klass == klass
1190 1191
        end

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

        while joins.first.is_a?(Arel::Nodes::Join)
          join_node = joins.shift
1202
          if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1203 1204 1205 1206
            buckets[:join_node] << join_node
          else
            buckets[:leading_join] << join_node
          end
1207 1208
        end

1209 1210
        buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
          if join.is_a?(Arel::Nodes::Join)
1211
            buckets[:join_node] << join
1212 1213 1214 1215
          else
            raise "unknown class: %s" % join.class.name
          end
        end
1216

1217 1218 1219
        buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
        buckets[:stashed_join] << stashed_eager_load if stashed_eager_load

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

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

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

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

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

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

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

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

1272
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1273
          arel_attribute(field)
1274 1275 1276
        elsif field.match?(/\A\w+\.\w+\z/)
          table, column = field.split(".")
          predicate_builder.resolve_arel_attribute(table, column)
1277
        else
1278
          yield field
1279 1280 1281
        end
      end

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

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

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

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

1326 1327
      def build_order(arel)
        orders = order_values.uniq
1328
        orders.compact_blank!
1329

1330 1331
        arel.order(*orders) unless orders.empty?
      end
1332

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

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

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

1355 1356
        validate_order_args(order_args)

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

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

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

1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428
      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

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

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

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