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

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

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
    private
      # Returns a relation value with a given name
      def get_value(name)
        @values.fetch(name, DEFAULT_VALUES[name])
      end
1000

1001
      # Sets the relation value with the given name
1002
      def set_value(name, value)
1003 1004 1005
        assert_mutability!
        @values[name] = value
      end
1006

1007 1008 1009 1010
      def assert_mutability!
        raise ImmutableRelation if @loaded
        raise ImmutableRelation if defined?(@arel) && @arel
      end
1011

1012
      def build_arel(aliases)
1013
        arel = Arel::SelectManager.new(table)
1014

1015
        aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
1016
        build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
1017

1018 1019
        arel.where(where_clause.ast) unless where_clause.empty?
        arel.having(having_clause.ast) unless having_clause.empty?
1020
        if limit_value
1021
          limit_attribute = ActiveModel::Attribute.with_cast_value(
1022
            "LIMIT",
1023 1024 1025 1026 1027 1028
            connection.sanitize_limit(limit_value),
            Type.default_value,
          )
          arel.take(Arel::Nodes::BindParam.new(limit_attribute))
        end
        if offset_value
1029
          offset_attribute = ActiveModel::Attribute.with_cast_value(
1030
            "OFFSET",
1031 1032 1033 1034 1035
            offset_value.to_i,
            Type.default_value,
          )
          arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
        end
1036
        arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1037

1038
        build_order(arel)
1039

1040
        build_select(arel)
1041

R
Ryuta Kamizono 已提交
1042
        arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1043 1044 1045
        arel.distinct(distinct_value)
        arel.from(build_from) unless from_clause.empty?
        arel.lock(lock_value) if lock_value
1046
        arel.comment(*annotate_values) unless annotate_values.empty?
1047

1048
        arel
1049 1050
      end

1051 1052 1053 1054 1055
      def build_from
        opts = from_clause.value
        name = from_clause.name
        case opts
        when Relation
1056 1057 1058
          if opts.eager_loading?
            opts = opts.send(:apply_join_dependency)
          end
1059 1060
          name ||= "subquery"
          opts.arel.as(name.to_s)
1061
        else
1062
          opts
1063 1064 1065
        end
      end

1066
      def build_left_outer_joins(manager, outer_joins, aliases)
1067 1068 1069 1070
        buckets = outer_joins.group_by do |join|
          case join
          when Hash, Symbol, Array
            :association_join
1071 1072
          when ActiveRecord::Associations::JoinDependency
            :stashed_join
1073 1074 1075
          else
            raise ArgumentError, "only Hash, Symbol and Array are allowed"
          end
A
Aaron Patterson 已提交
1076
        end
1077

1078
        build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1079
      end
1080

1081
      def build_joins(manager, joins, aliases)
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
        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
1096

1097
        build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1098
      end
1099

1100
      def build_join_query(manager, buckets, join_type, aliases)
1101
        buckets.default = []
1102

1103 1104 1105 1106
        association_joins = buckets[:association_join]
        stashed_joins     = buckets[:stashed_join]
        join_nodes        = buckets[:join_node].uniq
        string_joins      = buckets[:string_join].map(&:strip).uniq
1107

1108
        join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1109
        alias_tracker = alias_tracker(join_list, aliases)
1110

1111
        join_dependency = construct_join_dependency(association_joins)
1112

1113
        joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1114
        joins.each { |join| manager.from(join) }
1115

1116
        manager.join_sources.concat(join_list)
1117

1118
        alias_tracker.aliases
1119
      end
1120

1121
      def convert_join_strings_to_ast(joins)
1122 1123 1124 1125
        joins
          .flatten
          .reject(&:blank?)
          .map { |join| table.create_string_join(Arel.sql(join)) }
1126 1127
      end

1128 1129 1130
      def build_select(arel)
        if select_values.any?
          arel.project(*arel_columns(select_values.uniq))
1131 1132
        elsif klass.ignored_columns.any?
          arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1133
        else
R
Ryuta Kamizono 已提交
1134
          arel.project(table[Arel.star])
1135 1136 1137
        end
      end

1138
      def arel_columns(columns)
1139
        columns.flat_map do |field|
1140 1141 1142 1143 1144 1145 1146
          case field
          when Symbol
            field = field.to_s
            arel_column(field) { connection.quote_table_name(field) }
          when String
            arel_column(field) { field }
          when Proc
1147
            field.call
1148 1149 1150 1151
          else
            field
          end
        end
1152
      end
B
Brian Mathiyakom 已提交
1153

1154 1155
      def arel_column(field)
        field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1156
        from = from_clause.name || from_clause.value
1157

1158
        if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1159 1160 1161 1162 1163 1164
          arel_attribute(field)
        else
          yield
        end
      end

1165 1166 1167 1168
      def table_name_matches?(from)
        /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
      end

1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
      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)
1184
              raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1185 1186 1187
            end
            o.split(",").map! do |s|
              s.strip!
1188
              s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
1189 1190 1191
            end
          else
            o
1192
          end
1193
        end
1194
      end
1195

1196
      def does_not_support_reverse?(order)
1197 1198 1199 1200
        # Account for String subclasses like Arel::Nodes::SqlLiteral that
        # override methods like #count.
        order = String.new(order) unless order.instance_of?(String)

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

1207 1208 1209
      def build_order(arel)
        orders = order_values.uniq
        orders.reject!(&:blank?)
1210

1211 1212
        arel.order(*orders) unless orders.empty?
      end
1213

1214
      VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
1215
                          "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
1216

1217 1218 1219 1220
      def validate_order_args(args)
        args.each do |arg|
          next unless arg.is_a?(Hash)
          arg.each do |_key, value|
1221 1222 1223 1224
            unless VALID_DIRECTIONS.include?(value)
              raise ArgumentError,
                "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
            end
1225
          end
1226 1227
        end
      end
1228

1229 1230
      def preprocess_order_args(order_args)
        order_args.map! do |arg|
1231
          klass.sanitize_sql_for_order(arg)
1232
        end
1233
        order_args.flatten!
1234

1235
        @klass.disallow_raw_sql!(
1236
          order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1237
          permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
1238 1239
        )

1240 1241 1242
        validate_order_args(order_args)

        references = order_args.grep(String)
1243
        references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1244 1245 1246 1247 1248 1249
        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
1250 1251 1252
            arg = arg.to_s
            arel_column(arg) {
              Arel.sql(connection.quote_table_name(arg))
1253
            }.asc
1254 1255
          when Hash
            arg.map { |field, dir|
1256 1257 1258 1259
              case field
              when Arel::Nodes::SqlLiteral
                field.send(dir.downcase)
              else
1260 1261 1262 1263
                field = field.to_s
                arel_column(field) {
                  Arel.sql(connection.quote_table_name(field))
                }.send(dir.downcase)
1264
              end
1265 1266 1267 1268 1269 1270
            }
          else
            arg
          end
        end.flatten!
      end
1271

1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287
      # 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
1288 1289 1290 1291
      def check_if_method_has_arguments!(method_name, args)
        if args.blank?
          raise ArgumentError, "The method .#{method_name}() must contain arguments."
        end
1292
      end
S
Sean Griffin 已提交
1293

1294
      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1295
      def structurally_incompatible_values_for_or(other)
1296
        values = other.values
1297
        STRUCTURAL_OR_METHODS.reject do |method|
1298
          get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
1299
        end
1300
      end
1301

1302 1303 1304 1305
      def where_clause_factory
        @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
      end
      alias having_clause_factory where_clause_factory
1306

1307 1308 1309 1310 1311 1312 1313 1314 1315 1316
      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
1317 1318
  end
end