query_methods.rb 44.4 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 255
      fields.flatten!
      self.select_values += fields
256
      self
257
    end
S
Santiago Pastorino 已提交
258

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

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

J
Jon Leighton 已提交
305
    def group!(*args) # :nodoc:
306 307 308
      args.flatten!

      self.group_values += args
309
      self
310
    end
311

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

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

340
      self.order_values += args
341
      self
342
    end
343

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

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

362
      self.reordering_value = true
363
      self.order_values = args
364
      self
S
Sebastian Martinez 已提交
365 366
    end

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

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

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

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

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

      self
    end

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

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

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

    def left_outer_joins!(*args) # :nodoc:
491 492
      args.compact!
      args.flatten!
493 494 495 496
      self.left_outer_joins_values += args
      self
    end

497 498 499 500 501 502 503 504 505 506
    # 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
507
    # constructor as an SQL fragment, and used in the where clause of the query.
508 509 510 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
    #
    #    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,
540
    # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
541 542 543 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
    #
    #   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')
    #
572 573 574 575 576 577
    # 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 已提交
578 579
    #    Post.where(author: author)
    #    Post.where(author_id: author)
580 581 582
    #
    # This also works with polymorphic belongs_to relationships:
    #
A
AvnerCohen 已提交
583 584
    #    treasure = Treasure.create(name: 'gold coins')
    #    treasure.price_estimates << PriceEstimate.create(price: 125)
585 586
    #
    #    # The following queries will be equivalent:
A
AvnerCohen 已提交
587 588
    #    PriceEstimate.where(estimate_of: treasure)
    #    PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
589
    #
590 591 592 593 594 595 596 597 598 599
    # === 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 已提交
600
    #    User.joins(:posts).where({ posts: { published: true } })
601
    #
602
    # === no argument
603
    #
604 605
    # 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.
606 607
    #
    #    User.where.not(name: "Jon")
608
    #    # SELECT * FROM users WHERE name != 'Jon'
609
    #
610
    # See WhereChain for more details on #not.
611
    #
612
    # === blank condition
613
    #
614
    # If the condition is any blank-ish object, then #where is a no-op and returns
615
    # the current relation.
616
    def where(opts = :chain, *rest)
617
      if :chain == opts
618 619 620 621 622 623
        WhereChain.new(spawn)
      elsif opts.blank?
        self
      else
        spawn.where!(opts, *rest)
      end
624 625
    end

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

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

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

665 666 667
      spawn.or!(other)
    end

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

675
      self.where_clause = self.where_clause.or(other.where_clause)
676
      self.having_clause = having_clause.or(other.having_clause)
677
      self.references_values += other.references_values
678 679 680 681

      self
    end

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

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

694
      self.having_clause += having_clause_factory.build(opts, rest)
695
      self
696 697
    end

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

J
Jon Leighton 已提交
707
    def limit!(value) # :nodoc:
708 709
      self.limit_value = value
      self
710 711
    end

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

J
Jon Leighton 已提交
723
    def offset!(value) # :nodoc:
724 725
      self.offset_value = value
      self
726 727
    end

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

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

742
      self
743 744
    end

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

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

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

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

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

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

821
      self
822 823
    end

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

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

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

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

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

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

914
      self.extending_values += modules
915
      extend(*extending_values) if extending_values.any?
916

917
      self
918 919
    end

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

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

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

957 958
    def skip_query_cache!(value = true) # :nodoc:
      self.skip_query_cache_value = value
959 960 961
      self
    end

962 963 964 965 966
    def skip_preloading! # :nodoc:
      self.skip_preloading_value = true
      self
    end

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

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

992 993 994 995 996 997
    def construct_join_dependency(associations) # :nodoc:
      ActiveRecord::Associations::JoinDependency.new(
        klass, table, associations
      )
    end

998 999 1000 1001 1002 1003 1004 1005 1006
    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

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

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

1019 1020 1021 1022
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1023

1024
      def build_arel(aliases)
1025
        arel = Arel::SelectManager.new(table)
1026

1027 1028 1029 1030 1031
        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
1032

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

1053
        build_order(arel)
1054

1055
        build_select(arel)
1056

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

1063
        arel
1064 1065
      end

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

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

1092 1093
      def build_left_outer_joins(manager, outer_joins, aliases)
        buckets = { association_join: valid_association_list(outer_joins) }
1094
        build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1095
      end
1096

1097
      def build_joins(manager, joins, aliases)
1098 1099 1100 1101 1102
        unless left_outer_joins_values.empty?
          left_joins = valid_association_list(left_outer_joins_values.flatten)
          joins << construct_join_dependency(left_joins)
        end

1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116
        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
1117

1118
        build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1119
      end
1120

1121
      def build_join_query(manager, buckets, join_type, aliases)
1122
        buckets.default = []
1123

1124 1125 1126 1127
        association_joins = buckets[:association_join]
        stashed_joins     = buckets[:stashed_join]
        join_nodes        = buckets[:join_node].uniq
        string_joins      = buckets[:string_join].map(&:strip).uniq
1128

1129
        join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1130
        alias_tracker = alias_tracker(join_list, aliases)
1131

1132
        join_dependency = construct_join_dependency(association_joins)
1133

1134
        joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1135
        joins.each { |join| manager.from(join) }
1136

1137
        manager.join_sources.concat(join_list)
1138

1139
        alias_tracker.aliases
1140
      end
1141

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

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

1159
      def arel_columns(columns)
1160
        columns.flat_map do |field|
1161 1162 1163
          case field
          when Symbol
            field = field.to_s
1164
            arel_column(field, &connection.method(:quote_table_name))
1165
          when String
1166
            arel_column(field, &:itself)
1167
          when Proc
1168
            field.call
1169 1170 1171 1172
          else
            field
          end
        end
1173
      end
B
Brian Mathiyakom 已提交
1174

1175 1176
      def arel_column(field)
        field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1177
        from = from_clause.name || from_clause.value
1178

1179
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1180 1181
          arel_attribute(field)
        else
1182
          yield field
1183 1184 1185
        end
      end

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

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

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

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

1228 1229 1230
      def build_order(arel)
        orders = order_values.uniq
        orders.reject!(&:blank?)
1231

1232 1233
        arel.order(*orders) unless orders.empty?
      end
1234

1235
      VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1236
                          "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1237

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

1250 1251
      def preprocess_order_args(order_args)
        order_args.map! do |arg|
1252
          klass.sanitize_sql_for_order(arg)
1253
        end
1254
        order_args.flatten!
1255

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

1261 1262 1263
        validate_order_args(order_args)

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

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

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

1323 1324 1325 1326
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1327

1328 1329 1330 1331 1332 1333 1334 1335 1336 1337
      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
1338 1339
  end
end