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

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

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

13 14
    include ActiveModel::ForbiddenAttributesProtection

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

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

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

50
        where_clause = @scope.send(:where_clause_factory).build(opts, rest)
51 52

        @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
53
        @scope.where_clause += where_clause.invert
54 55 56 57
        @scope
      end
    end

58
    FROZEN_EMPTY_ARRAY = [].freeze
59
    FROZEN_EMPTY_HASH = {}.freeze
60

61
    Relation::VALUE_METHODS.each do |name|
62 63 64 65 66 67
      method_name = \
        case name
        when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
        when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
        when *Relation::CLAUSE_METHODS then "#{name}_clause"
        end
68
      class_eval <<-CODE, __FILE__, __LINE__ + 1
69 70
        def #{method_name}                   # def includes_values
          get_value(#{name.inspect})         #   get_value(:includes)
71
        end                                  # end
72

73 74
        def #{method_name}=(value)           # def includes_values=(value)
          set_value(#{name.inspect}, value)  #   set_value(:includes, value)
75 76
        end                                  # end
      CODE
77 78
    end

79
    alias extensions extending_values
80

O
Oscar Del Ben 已提交
81 82 83 84 85 86 87 88 89 90
    # 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
91
    # performance improvement over a simple join.
92
    #
93 94 95 96 97 98 99 100
    # 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])
    #
101 102
    # === conditions
    #
103
    # If you want to add string conditions to your included models, you'll have
104 105 106 107 108 109 110
    # 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)
111
    #
112
    # Note that #includes works with association names while #references needs
113
    # the actual table name.
114 115 116 117 118
    #
    # 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:
    #
119
    #   User.includes(:posts).where(posts: { name: 'example' })
120
    def includes(*args)
121
      check_if_method_has_arguments!(:includes, args)
122
      spawn.includes!(*args)
123
    end
124

J
Jon Leighton 已提交
125
    def includes!(*args) # :nodoc:
126 127
      args.reject!(&:blank?)
      args.flatten!
A
Aaron Patterson 已提交
128

129
      self.includes_values |= args
130
      self
131
    end
132

133 134 135
    # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
    #
    #   User.eager_load(:posts)
136 137 138
    #   # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
    #   # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
    #   # "users"."id"
139
    def eager_load(*args)
140
      check_if_method_has_arguments!(:eager_load, args)
141
      spawn.eager_load!(*args)
142
    end
143

J
Jon Leighton 已提交
144
    def eager_load!(*args) # :nodoc:
145 146
      self.eager_load_values += args
      self
147 148
    end

149
    # Allows preloading of +args+, in the same way that #includes does:
150 151
    #
    #   User.preload(:posts)
152
    #   # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
153
    def preload(*args)
154
      check_if_method_has_arguments!(:preload, args)
155
      spawn.preload!(*args)
156
    end
157

J
Jon Leighton 已提交
158
    def preload!(*args) # :nodoc:
159 160
      self.preload_values += args
      self
161
    end
162

163 164 165 166 167 168 169 170 171 172 173 174 175
    # 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

176 177
    # 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.
178
    # This method only works in conjunction with #includes.
179
    # See #includes for more details.
180 181
    #
    #   User.includes(:posts).where("posts.name = 'foo'")
182
    #   # Doesn't JOIN the posts table, resulting in an error.
183 184
    #
    #   User.includes(:posts).where("posts.name = 'foo'").references(:posts)
185
    #   # Query now knows the string references posts, so adds a JOIN
186 187 188
    def references(*table_names)
      check_if_method_has_arguments!(:references, table_names)
      spawn.references!(*table_names)
189
    end
190

191 192 193
    def references!(*table_names) # :nodoc:
      table_names.flatten!
      table_names.map!(&:to_s)
194

195
      self.references_values |= table_names
196
      self
197 198
    end

199
    # Works in two unique ways.
200
    #
201
    # First: takes a block so it can be used just like <tt>Array#select</tt>.
202
    #
203
    #   Model.all.select { |m| m.field == value }
204 205
    #
    # This will build an array of objects from the database for the scope,
206 207
    # converting them into an array and iterating through them using
    # <tt>Array#select</tt>.
208 209
    #
    # Second: Modifies the SELECT statement for the query so that only certain
V
Vijay Dev 已提交
210
    # fields are retrieved:
211
    #
212
    #   Model.select(:field)
213
    #   # => [#<Model id: nil, field: "value">]
214 215
    #
    # Although in the above example it looks as though this method returns an
V
Vijay Dev 已提交
216
    # array, it actually returns a relation object and can have other query
217 218
    # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
    #
219
    # The argument to the method can also be an array of fields.
220
    #
221
    #   Model.select(:field, :other_field, :and_one_more)
222
    #   # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
223
    #
224 225 226
    # 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')
227
    #   # => [#<Model id: nil, field: "value", other_field: "value">]
228 229 230 231 232 233
    #
    # If an alias was specified, it will be accessible from the resulting objects:
    #
    #   Model.select('field AS field_one').first.field_one
    #   # => "value"
    #
234
    # Accessing attributes of an object that do not have fields retrieved by a select
235
    # except +id+ will throw ActiveModel::MissingAttributeError:
236
    #
237 238 239
    #   Model.select(:field).first.other_field
    #   # => ActiveModel::MissingAttributeError: missing attribute: other_field
    def select(*fields)
240 241
      if block_given?
        if fields.any?
242
          raise ArgumentError, "`select' with block doesn't take arguments."
243 244 245 246 247
        end

        return super()
      end

248
      raise ArgumentError, "Call `select' with at least one field" if fields.empty?
249
      spawn._select!(*fields)
250 251
    end

252
    def _select!(*fields) # :nodoc:
253
      fields.reject!(&:blank?)
254
      fields.flatten!
255
      fields.map! do |field|
256
        klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
257
      end
258
      self.select_values += fields
259
      self
260
    end
S
Santiago Pastorino 已提交
261

W
Willian Gustavo Veiga 已提交
262 263 264
    # Allows you to change a previously set select statement.
    #
    #   Post.select(:title, :body)
265
    #   # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
W
Willian Gustavo Veiga 已提交
266 267
    #
    #   Post.select(:title, :body).reselect(:created_at)
268
    #   # SELECT `posts`.`created_at` FROM `posts`
W
Willian Gustavo Veiga 已提交
269 270 271
    #
    # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
    # Note that we're unscoping the entire select statement.
272 273 274 275 276 277 278 279 280
    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 已提交
281
    end
S
Santiago Pastorino 已提交
282

O
Oscar Del Ben 已提交
283 284 285
    # Allows to specify a group attribute:
    #
    #   User.group(:name)
286
    #   # SELECT "users".* FROM "users" GROUP BY name
O
Oscar Del Ben 已提交
287
    #
288
    # Returns an array with distinct records based on the +group+ attribute:
O
Oscar Del Ben 已提交
289 290
    #
    #   User.select([:id, :name])
291
    #   # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
O
Oscar Del Ben 已提交
292 293
    #
    #   User.group(:name)
294
    #   # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
295 296
    #
    #   User.group('name AS grouped_name, age')
297
    #   # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
298 299
    #
    # Passing in an array of attributes to group by is also supported.
300
    #
301
    #   User.select([:id, :first_name]).group(:id, :first_name).first(3)
302
    #   # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
303
    def group(*args)
304
      check_if_method_has_arguments!(:group, args)
305
      spawn.group!(*args)
306
    end
307

J
Jon Leighton 已提交
308
    def group!(*args) # :nodoc:
309 310 311
      args.flatten!

      self.group_values += args
312
      self
313
    end
314

O
Oscar Del Ben 已提交
315 316
    # Allows to specify an order attribute:
    #
317
    #   User.order(:name)
318
    #   # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
319
    #
320
    #   User.order(email: :desc)
321
    #   # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
322
    #
323
    #   User.order(:name, email: :desc)
324
    #   # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
325 326
    #
    #   User.order('name')
327
    #   # SELECT "users".* FROM "users" ORDER BY name
328 329
    #
    #   User.order('name DESC')
330
    #   # SELECT "users".* FROM "users" ORDER BY name DESC
331 332
    #
    #   User.order('name DESC, email')
333
    #   # SELECT "users".* FROM "users" ORDER BY name DESC, email
334
    def order(*args)
335
      check_if_method_has_arguments!(:order, args)
336
      spawn.order!(*args)
337
    end
338

339
    # Same as #order but operates on relation in-place instead of copying.
J
Jon Leighton 已提交
340
    def order!(*args) # :nodoc:
341
      preprocess_order_args(args)
342

343
      self.order_values += args
344
      self
345
    end
346

347 348 349 350 351 352 353 354
    # 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')
    #
355
    # generates a query with 'ORDER BY id ASC, name ASC'.
S
Sebastian Martinez 已提交
356
    def reorder(*args)
357
      check_if_method_has_arguments!(:reorder, args)
358
      spawn.reorder!(*args)
359
    end
360

361
    # Same as #reorder but operates on relation in-place instead of copying.
J
Jon Leighton 已提交
362
    def reorder!(*args) # :nodoc:
363
      preprocess_order_args(args)
364

365
      self.reordering_value = true
366
      self.order_values = args
367
      self
S
Sebastian Martinez 已提交
368 369
    end

370
    VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
371
                                     :limit, :offset, :joins, :left_outer_joins, :annotate,
372
                                     :includes, :from, :readonly, :having, :optimizer_hints])
373 374 375 376 377

    # 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.
    #
378
    #   User.order('email DESC').unscope(:order) == User.all
379 380 381 382 383
    #
    # 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:
    #
384
    #   User.order('email DESC').select('id').where(name: "John")
385 386
    #       .unscope(:order, :select, :where) == User.all
    #
387
    # One can additionally pass a hash as an argument to unscope specific +:where+ values.
388
    # This is done by passing a hash with a single key-value pair. The key should be
389
    # +:where+ and the value should be the where value to unscope. For example:
390
    #
391 392
    #   User.where(name: "John", active: true).unscope(where: :name)
    #       == User.where(active: true)
393
    #
394 395
    # This method is similar to #except, but unlike
    # #except, it persists across merges:
396
    #
J
Jon Leighton 已提交
397 398
    #   User.order('email').merge(User.except(:order))
    #       == User.order('email')
399
    #
J
Jon Leighton 已提交
400 401 402 403 404
    #   User.order('email').merge(User.unscope(:order))
    #       == User.all
    #
    # This means it can be used in association definitions:
    #
R
Ryuta Kamizono 已提交
405
    #   has_many :comments, -> { unscope(where: :trashed) }
406 407
    #
    def unscope(*args)
408
      check_if_method_has_arguments!(:unscope, args)
409 410 411
      spawn.unscope!(*args)
    end

412
    def unscope!(*args) # :nodoc:
413
      args.flatten!
J
Jon Leighton 已提交
414
      self.unscope_values += args
415 416 417 418

      args.each do |scope|
        case scope
        when Symbol
419
          scope = :left_outer_joins if scope == :left_joins
420 421 422
          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
423
          set_value(scope, DEFAULT_VALUES[scope])
424 425 426 427 428 429
        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

430 431
            target_values = Array(target_value).map(&:to_s)
            self.where_clause = where_clause.except(*target_values)
432 433 434 435 436 437 438 439 440
          end
        else
          raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
        end
      end

      self
    end

441 442
    # Performs a joins on +args+. The given symbol(s) should match the name of
    # the association(s).
443 444
    #
    #   User.joins(:posts)
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
    #   # 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"
465 466 467 468
    #
    # 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")
469
    #   # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
470
    def joins(*args)
471 472
      check_if_method_has_arguments!(:joins, args)
      spawn.joins!(*args)
473
    end
474

J
Jon Leighton 已提交
475
    def joins!(*args) # :nodoc:
476 477
      args.compact!
      args.flatten!
478 479
      self.joins_values += args
      self
P
Pratik Naik 已提交
480 481
    end

482 483 484 485 486 487
    # 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)
488
      check_if_method_has_arguments!(__callee__, args)
489 490
      spawn.left_outer_joins!(*args)
    end
491
    alias :left_joins :left_outer_joins
492 493

    def left_outer_joins!(*args) # :nodoc:
494 495
      args.compact!
      args.flatten!
496 497 498 499
      self.left_outer_joins_values += args
      self
    end

500 501 502 503 504 505 506 507 508 509
    # 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
510
    # constructor as an SQL fragment, and used in the where clause of the query.
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
    #
    #    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,
543
    # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
    #
    #   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')
    #
575 576 577 578 579 580
    # 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 已提交
581 582
    #    Post.where(author: author)
    #    Post.where(author_id: author)
583 584 585
    #
    # This also works with polymorphic belongs_to relationships:
    #
A
AvnerCohen 已提交
586 587
    #    treasure = Treasure.create(name: 'gold coins')
    #    treasure.price_estimates << PriceEstimate.create(price: 125)
588 589
    #
    #    # The following queries will be equivalent:
A
AvnerCohen 已提交
590 591
    #    PriceEstimate.where(estimate_of: treasure)
    #    PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
592
    #
593 594 595 596 597 598 599 600 601 602
    # === 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 已提交
603
    #    User.joins(:posts).where({ posts: { published: true } })
604
    #
605
    # === no argument
606
    #
607 608
    # 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.
609 610
    #
    #    User.where.not(name: "Jon")
611
    #    # SELECT * FROM users WHERE name != 'Jon'
612
    #
613
    # See WhereChain for more details on #not.
614
    #
615
    # === blank condition
616
    #
617
    # If the condition is any blank-ish object, then #where is a no-op and returns
618
    # the current relation.
619
    def where(opts = :chain, *rest)
620
      if :chain == opts
621 622 623 624 625 626
        WhereChain.new(spawn)
      elsif opts.blank?
        self
      else
        spawn.where!(opts, *rest)
      end
627 628
    end

629
    def where!(opts, *rest) # :nodoc:
630 631
      opts = sanitize_forbidden_attributes(opts)
      references!(PredicateBuilder.references(opts)) if Hash === opts
632
      self.where_clause += where_clause_factory.build(opts, rest)
633
      self
634
    end
P
Pratik Naik 已提交
635

636 637
    # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
    #
638 639 640 641 642 643 644 645
    #   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
646
    #
647 648
    # 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.
649 650 651 652
    def rewhere(conditions)
      unscope(where: conditions.keys).where(conditions)
    end

653 654 655 656
    # 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
657 658
    # 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.
659
    #
660
    #    Post.where("id = 1").or(Post.where("author_id = 3"))
R
Ryuta Kamizono 已提交
661
    #    # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
662 663
    #
    def or(other)
664 665 666 667
      unless other.is_a? Relation
        raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
      end

668 669 670
      spawn.or!(other)
    end

671
    def or!(other) # :nodoc:
672 673 674 675
      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}"
676 677
      end

678
      self.where_clause = self.where_clause.or(other.where_clause)
679
      self.having_clause = having_clause.or(other.having_clause)
680
      self.references_values += other.references_values
681 682 683 684

      self
    end

685 686 687 688
    # 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')
689
    def having(opts, *rest)
690
      opts.blank? ? self : spawn.having!(opts, *rest)
691 692
    end

J
Jon Leighton 已提交
693
    def having!(opts, *rest) # :nodoc:
694
      opts = sanitize_forbidden_attributes(opts)
695
      references!(PredicateBuilder.references(opts)) if Hash === opts
696

697
      self.having_clause += having_clause_factory.build(opts, rest)
698
      self
699 700
    end

701
    # Specifies a limit for the number of records to retrieve.
702 703 704 705
    #
    #   User.limit(10) # generated SQL has 'LIMIT 10'
    #
    #   User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
706
    def limit(value)
J
Jon Leighton 已提交
707
      spawn.limit!(value)
708 709
    end

J
Jon Leighton 已提交
710
    def limit!(value) # :nodoc:
711 712
      self.limit_value = value
      self
713 714
    end

715 716 717 718
    # Specifies the number of rows to skip before returning rows.
    #
    #   User.offset(10) # generated SQL has "OFFSET 10"
    #
719
    # Should be used with order.
720
    #
721
    #   User.offset(10).order("name ASC")
722
    def offset(value)
J
Jon Leighton 已提交
723
      spawn.offset!(value)
724 725
    end

J
Jon Leighton 已提交
726
    def offset!(value) # :nodoc:
727 728
      self.offset_value = value
      self
729 730
    end

731
    # Specifies locking settings (default to +true+). For more information
732
    # on locking, please see ActiveRecord::Locking.
733
    def lock(locks = true)
J
Jon Leighton 已提交
734
      spawn.lock!(locks)
735
    end
736

J
Jon Leighton 已提交
737
    def lock!(locks = true) # :nodoc:
738
      case locks
739
      when String, TrueClass, NilClass
740
        self.lock_value = locks || true
741
      else
742
        self.lock_value = false
743
      end
744

745
      self
746 747
    end

748
    # Returns a chainable relation with zero records.
749
    #
750 751 752
    # 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.
753 754 755 756
    #
    # Any subsequent condition chained to the returned relation will continue
    # generating an empty relation and will not fire any query to the database.
    #
757 758
    # Used in cases where a method or scope could return zero records but the
    # result needs to be chainable.
759 760 761
    #
    # For example:
    #
A
AvnerCohen 已提交
762
    #   @posts = current_user.visible_posts.where(name: params[:name])
763
    #   # the visible_posts method is expected to return a chainable Relation
764 765 766
    #
    #   def visible_posts
    #     case role
767
    #     when 'Country Manager'
A
AvnerCohen 已提交
768
    #       Post.where(country: country)
769
    #     when 'Reviewer'
770
    #       Post.published
771
    #     when 'Bad User'
772
    #       Post.none # It can't be chained if [] is returned.
773 774 775 776
    #     end
    #   end
    #
    def none
777
      spawn.none!
778 779
    end

J
Jon Leighton 已提交
780
    def none! # :nodoc:
781
      where!("1=0").extending!(NullRelation)
782 783
    end

784 785 786 787 788
    # 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
789
    #   => ActiveRecord::ReadOnlyRecord: User is marked as readonly
790
    def readonly(value = true)
J
Jon Leighton 已提交
791
      spawn.readonly!(value)
792 793
    end

J
Jon Leighton 已提交
794
    def readonly!(value = true) # :nodoc:
795 796
      self.readonly_value = value
      self
797 798
    end

799 800 801 802 803 804 805 806 807
    # 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'
    #
808
    # You can pass +nil+ to #create_with to reset attributes:
809 810 811
    #
    #   users = users.create_with(nil)
    #   users.new.name # => 'Oscar'
812
    def create_with(value)
J
Jon Leighton 已提交
813
      spawn.create_with!(value)
814 815
    end

J
Jon Leighton 已提交
816
    def create_with!(value) # :nodoc:
817 818 819 820
      if value
        value = sanitize_forbidden_attributes(value)
        self.create_with_value = create_with_value.merge(value)
      else
821
        self.create_with_value = FROZEN_EMPTY_HASH
822 823
      end

824
      self
825 826
    end

827 828 829
    # Specifies table from which the records will be fetched. For example:
    #
    #   Topic.select('title').from('posts')
830
    #   # SELECT title FROM posts
831 832 833
    #
    # Can accept other relation objects. For example:
    #
834
    #   Topic.select('title').from(Topic.approved)
835
    #   # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
836
    #
837
    #   Topic.select('a.title').from(Topic.approved, :a)
838
    #   # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
839 840 841
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
842 843
    end

J
Jon Leighton 已提交
844
    def from!(value, subquery_name = nil) # :nodoc:
845
      self.from_clause = Relation::FromClause.new(value, subquery_name)
846
      self
847 848
    end

849 850 851
    # Specifies whether the records should be unique or not. For example:
    #
    #   User.select(:name)
852
    #   # Might return two records with the same name
853
    #
854
    #   User.select(:name).distinct
855
    #   # Returns 1 record per distinct name
856
    #
857
    #   User.select(:name).distinct.distinct(false)
858
    #   # You can also remove the uniqueness
859 860
    def distinct(value = true)
      spawn.distinct!(value)
861 862
    end

863 864 865
    # Like #distinct, but modifies relation in place.
    def distinct!(value = true) # :nodoc:
      self.distinct_value = value
866
      self
867 868
    end

869
    # Used to extend a scope with additional methods, either through
870 871
    # a module or through a block provided.
    #
872 873 874 875 876 877 878 879 880 881
    # 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
    #
882
    #   scope = Model.all.extending(Pagination)
883 884
    #   scope.page(params[:page])
    #
V
Vijay Dev 已提交
885
    # You can also pass a list of modules:
886
    #
887
    #   scope = Model.all.extending(Pagination, SomethingElse)
888 889 890
    #
    # === Using a block
    #
891
    #   scope = Model.all.extending do
892
    #     def page(number)
893
    #       # pagination code goes here
894 895 896 897 898 899
    #     end
    #   end
    #   scope.page(params[:page])
    #
    # You can also use a block and a module list:
    #
900
    #   scope = Model.all.extending(Pagination) do
901
    #     def per_page(number)
902
    #       # pagination code goes here
903 904
    #     end
    #   end
905 906
    def extending(*modules, &block)
      if modules.any? || block
J
Jon Leighton 已提交
907
        spawn.extending!(*modules, &block)
908 909 910 911
      else
        self
      end
    end
912

J
Jon Leighton 已提交
913
    def extending!(*modules, &block) # :nodoc:
914 915
      modules << Module.new(&block) if block
      modules.flatten!
916

917
      self.extending_values += modules
918
      extend(*extending_values) if extending_values.any?
919

920
      self
921 922
    end

R
Ryuta Kamizono 已提交
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
    # 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!

      self.optimizer_hints_values += args
      self
    end

946 947 948
    # Reverse the existing order clause on the relation.
    #
    #   User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
949
    def reverse_order
J
Jon Leighton 已提交
950
      spawn.reverse_order!
951 952
    end

J
Jon Leighton 已提交
953
    def reverse_order! # :nodoc:
954 955 956
      orders = order_values.uniq
      orders.reject!(&:blank?)
      self.order_values = reverse_sql_order(orders)
957
      self
958 959
    end

960 961
    def skip_query_cache!(value = true) # :nodoc:
      self.skip_query_cache_value = value
962 963 964
      self
    end

965 966 967 968 969
    def skip_preloading! # :nodoc:
      self.skip_preloading_value = true
      self
    end

970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
    # 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

990
    # Returns the Arel object associated with the relation.
991 992
    def arel(aliases = nil) # :nodoc:
      @arel ||= build_arel(aliases)
993 994
    end

995 996 997 998 999 1000
    def construct_join_dependency(associations) # :nodoc:
      ActiveRecord::Associations::JoinDependency.new(
        klass, table, associations
      )
    end

1001 1002 1003 1004 1005 1006 1007 1008 1009
    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

1010 1011 1012 1013 1014
    private
      # Returns a relation value with a given name
      def get_value(name)
        @values.fetch(name, DEFAULT_VALUES[name])
      end
1015

1016
      # Sets the relation value with the given name
1017
      def set_value(name, value)
1018 1019 1020
        assert_mutability!
        @values[name] = value
      end
1021

1022 1023 1024 1025
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1026

1027
      def build_arel(aliases)
1028
        arel = Arel::SelectManager.new(table)
1029

1030 1031 1032 1033 1034
        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
1035

1036 1037
        arel.where(where_clause.ast) unless where_clause.empty?
        arel.having(having_clause.ast) unless having_clause.empty?
1038
        if limit_value
1039
          limit_attribute = ActiveModel::Attribute.with_cast_value(
1040
            "LIMIT",
1041 1042 1043 1044 1045 1046
            connection.sanitize_limit(limit_value),
            Type.default_value,
          )
          arel.take(Arel::Nodes::BindParam.new(limit_attribute))
        end
        if offset_value
1047
          offset_attribute = ActiveModel::Attribute.with_cast_value(
1048
            "OFFSET",
1049 1050 1051 1052 1053
            offset_value.to_i,
            Type.default_value,
          )
          arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
        end
1054
        arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1055

1056
        build_order(arel)
1057

1058
        build_select(arel)
1059

R
Ryuta Kamizono 已提交
1060
        arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1061 1062 1063
        arel.distinct(distinct_value)
        arel.from(build_from) unless from_clause.empty?
        arel.lock(lock_value) if lock_value
1064
        arel.comment(*annotate_values) unless annotate_values.empty?
1065

1066
        arel
1067 1068
      end

1069 1070 1071 1072 1073
      def build_from
        opts = from_clause.value
        name = from_clause.name
        case opts
        when Relation
1074 1075 1076
          if opts.eager_loading?
            opts = opts.send(:apply_join_dependency)
          end
1077 1078
          name ||= "subquery"
          opts.arel.as(name.to_s)
1079
        else
1080
          opts
1081 1082 1083
        end
      end

1084 1085 1086
      def valid_association_list(associations)
        associations.each do |association|
          case association
1087
          when Hash, Symbol, Array
1088
            # valid
1089 1090 1091
          else
            raise ArgumentError, "only Hash, Symbol and Array are allowed"
          end
A
Aaron Patterson 已提交
1092
        end
1093
      end
1094

1095 1096
      def build_left_outer_joins(manager, outer_joins, aliases)
        buckets = { association_join: valid_association_list(outer_joins) }
1097
        build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1098
      end
1099

1100
      def build_joins(manager, joins, aliases)
1101 1102 1103 1104 1105
        unless left_outer_joins_values.empty?
          left_joins = valid_association_list(left_outer_joins_values.flatten)
          joins << construct_join_dependency(left_joins)
        end

1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
        buckets = joins.group_by do |join|
          case join
          when String
            :string_join
          when Hash, Symbol, Array
            :association_join
          when ActiveRecord::Associations::JoinDependency
            :stashed_join
          when Arel::Nodes::Join
            :join_node
          else
            raise "unknown class: %s" % join.class.name
          end
        end
1120

1121
        build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1122
      end
1123

1124
      def build_join_query(manager, buckets, join_type, aliases)
1125
        buckets.default = []
1126

1127 1128 1129 1130
        association_joins = buckets[:association_join]
        stashed_joins     = buckets[:stashed_join]
        join_nodes        = buckets[:join_node].uniq
        string_joins      = buckets[:string_join].map(&:strip).uniq
1131

1132
        join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1133
        alias_tracker = alias_tracker(join_list, aliases)
1134

1135
        join_dependency = construct_join_dependency(association_joins)
1136

1137
        joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1138
        joins.each { |join| manager.from(join) }
1139

1140
        manager.join_sources.concat(join_list)
1141

1142
        alias_tracker.aliases
1143
      end
1144

1145
      def convert_join_strings_to_ast(joins)
1146 1147 1148 1149
        joins
          .flatten
          .reject(&:blank?)
          .map { |join| table.create_string_join(Arel.sql(join)) }
1150 1151
      end

1152 1153 1154
      def build_select(arel)
        if select_values.any?
          arel.project(*arel_columns(select_values.uniq))
1155 1156
        elsif klass.ignored_columns.any?
          arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1157
        else
R
Ryuta Kamizono 已提交
1158
          arel.project(table[Arel.star])
1159 1160 1161
        end
      end

1162
      def arel_columns(columns)
1163
        columns.flat_map do |field|
1164 1165 1166 1167 1168 1169 1170
          case field
          when Symbol
            field = field.to_s
            arel_column(field) { connection.quote_table_name(field) }
          when String
            arel_column(field) { field }
          when Proc
1171
            field.call
1172 1173 1174 1175
          else
            field
          end
        end
1176
      end
B
Brian Mathiyakom 已提交
1177

1178 1179
      def arel_column(field)
        field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1180
        from = from_clause.name || from_clause.value
1181

1182
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1183 1184 1185 1186 1187 1188
          arel_attribute(field)
        else
          yield
        end
      end

1189 1190 1191 1192
      def table_name_matches?(from)
        /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
      end

1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207
      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)
1208
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1209 1210 1211
            end
            o.split(",").map! do |s|
              s.strip!
1212
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1213 1214 1215
            end
          else
            o
1216
          end
1217
        end
1218
      end
1219

1220
      def does_not_support_reverse?(order)
1221 1222 1223 1224
        # Account for String subclasses like Arel::Nodes::SqlLiteral that
        # override methods like #count.
        order = String.new(order) unless order.instance_of?(String)

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

1231 1232 1233
      def build_order(arel)
        orders = order_values.uniq
        orders.reject!(&:blank?)
1234

1235 1236
        arel.order(*orders) unless orders.empty?
      end
1237

1238
      VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1239
                          "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1240

1241 1242 1243 1244
      def validate_order_args(args)
        args.each do |arg|
          next unless arg.is_a?(Hash)
          arg.each do |_key, value|
1245 1246 1247 1248
            unless VALID_DIRECTIONS.include?(value)
              raise ArgumentError,
                "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
            end
1249
          end
1250 1251
        end
      end
1252

1253 1254
      def preprocess_order_args(order_args)
        order_args.map! do |arg|
1255
          klass.sanitize_sql_for_order(arg)
1256
        end
1257
        order_args.flatten!
1258

1259
        @klass.disallow_raw_sql!(
1260
          order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1261
          permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
1262 1263
        )

1264 1265 1266
        validate_order_args(order_args)

        references = order_args.grep(String)
1267
        references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1268 1269 1270 1271 1272 1273
        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
1274 1275 1276
            arg = arg.to_s
            arel_column(arg) {
              Arel.sql(connection.quote_table_name(arg))
1277
            }.asc
1278 1279
          when Hash
            arg.map { |field, dir|
1280 1281 1282 1283
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
1284 1285 1286 1287
                field = field.to_s
                arel_column(field) {
                  Arel.sql(connection.quote_table_name(field))
                }.send(dir.downcase)
1288
              end
1289 1290 1291 1292 1293 1294
            }
          else
            arg
          end
        end.flatten!
      end
1295

1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311
      # 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
1312 1313 1314 1315
      def check_if_method_has_arguments!(method_name, args)
        if args.blank?
          raise ArgumentError, "The method .#{method_name}() must contain arguments."
        end
1316
      end
S
Sean Griffin 已提交
1317

1318
      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1319
      def structurally_incompatible_values_for_or(other)
1320
        values = other.values
1321
        STRUCTURAL_OR_METHODS.reject do |method|
1322
          get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
1323
        end
1324
      end
1325

1326 1327 1328 1329
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1330

1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
      DEFAULT_VALUES = {
        create_with: FROZEN_EMPTY_HASH,
        where: Relation::WhereClause.empty,
        having: Relation::WhereClause.empty,
        from: Relation::FromClause.empty
      }

      Relation::MULTI_VALUE_METHODS.each do |value|
        DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
      end
1341 1342
  end
end