activerecord 7.1.5.1 → 7.2.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -87,6 +87,14 @@ module ActiveRecord
87
87
  #
88
88
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
89
89
  # # returns an Array of the required fields.
90
+ #
91
+ # ==== Edge Cases
92
+ #
93
+ # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
94
+ # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
95
+ # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
96
+ # Person.find([]) # returns an empty array if the argument is an empty array.
97
+ # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
90
98
  def find(*args)
91
99
  return super if block_given?
92
100
  find_with_ids(*args)
@@ -366,7 +374,11 @@ module ActiveRecord
366
374
  relation = construct_relation_for_exists(conditions)
367
375
  return false if relation.where_clause.contradiction?
368
376
 
369
- skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
377
+ skip_query_cache_if_necessary do
378
+ with_connection do |c|
379
+ c.select_rows(relation.arel, "#{name} Exists?").size == 1
380
+ end
381
+ end
370
382
  end
371
383
 
372
384
  # Returns true if the relation contains the given record or false otherwise.
@@ -459,7 +471,9 @@ module ActiveRecord
459
471
  )
460
472
  )
461
473
  relation = skip_query_cache_if_necessary do
462
- klass.connection.distinct_relation_for_primary_key(relation)
474
+ klass.with_connection do |c|
475
+ c.distinct_relation_for_primary_key(relation)
476
+ end
463
477
  end
464
478
  end
465
479
 
@@ -7,16 +7,15 @@ module ActiveRecord
7
7
  class HashMerger # :nodoc:
8
8
  attr_reader :relation, :hash
9
9
 
10
- def initialize(relation, hash, rewhere = nil)
10
+ def initialize(relation, hash)
11
11
  hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
12
 
13
13
  @relation = relation
14
14
  @hash = hash
15
- @rewhere = rewhere
16
15
  end
17
16
 
18
17
  def merge
19
- Merger.new(relation, other, @rewhere).merge
18
+ Merger.new(relation, other).merge
20
19
  end
21
20
 
22
21
  # Applying values to a relation has some side effects. E.g.
@@ -44,11 +43,10 @@ module ActiveRecord
44
43
  class Merger # :nodoc:
45
44
  attr_reader :relation, :values, :other
46
45
 
47
- def initialize(relation, other, rewhere = nil)
46
+ def initialize(relation, other)
48
47
  @relation = relation
49
48
  @values = other.values
50
49
  @other = other
51
- @rewhere = rewhere
52
50
  end
53
51
 
54
52
  NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
@@ -178,7 +176,7 @@ module ActiveRecord
178
176
  def merge_clauses
179
177
  relation.from_clause = other.from_clause if replace_from_clause?
180
178
 
181
- where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
179
+ where_clause = relation.where_clause.merge(other.where_clause)
182
180
  relation.where_clause = where_clause unless where_clause.empty?
183
181
 
184
182
  having_clause = relation.having_clause.merge(other.having_clause)
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  return attribute.in([]) if value.empty?
14
14
 
15
15
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
- nils = values.extract!(&:nil?)
16
+ nils = values.compact!
17
17
  ranges = values.extract! { |v| v.is_a?(Range) }
18
18
 
19
19
  values_predicate =
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
24
24
  end
25
25
 
26
- unless nils.empty?
26
+ if nils
27
27
  values_predicate = values_predicate.or(attribute.eq(nil))
28
28
  end
29
29
 
@@ -28,9 +28,9 @@ module ActiveRecord
28
28
  def self.references(attributes)
29
29
  attributes.each_with_object([]) do |(key, value), result|
30
30
  if value.is_a?(Hash)
31
- result << Arel.sql(key)
31
+ result << Arel.sql(key, retryable: true)
32
32
  elsif (idx = key.rindex("."))
33
- result << Arel.sql(key[0, idx])
33
+ result << Arel.sql(key[0, idx], retryable: true)
34
34
  end
35
35
  end
36
36
  end
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
  queries.first
143
143
  else
144
144
  queries.map! { |query| query.reduce(&:and) }
145
- queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
145
+ queries = queries.reduce { |result, query| Arel::Nodes::Or.new([result, query]) }
146
146
  Arel::Nodes::Grouping.new(queries)
147
147
  end
148
148
  end
@@ -72,10 +72,26 @@ module ActiveRecord
72
72
  # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
73
73
  # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
74
74
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
75
+ #
76
+ # You can define join type in the scope and +associated+ will not use `JOIN` by default.
77
+ #
78
+ # Post.left_joins(:author).where.associated(:author)
79
+ # # SELECT "posts".* FROM "posts"
80
+ # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
81
+ # # WHERE "authors"."id" IS NOT NULL
82
+ #
83
+ # Post.left_joins(:comments).where.associated(:author)
84
+ # # SELECT "posts".* FROM "posts"
85
+ # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
86
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
87
+ # # WHERE "author"."id" IS NOT NULL
75
88
  def associated(*associations)
76
89
  associations.each do |association|
77
90
  reflection = scope_association_reflection(association)
78
- @scope.joins!(association)
91
+ unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
92
+ @scope.joins!(association)
93
+ end
94
+
79
95
  if reflection.options[:class_name]
80
96
  self.not(association => { reflection.association_primary_key => nil })
81
97
  else
@@ -419,6 +435,17 @@ module ActiveRecord
419
435
  # # )
420
436
  # # SELECT * FROM posts
421
437
  #
438
+ # You can also pass an array of sub-queries to be joined in a +UNION ALL+.
439
+ #
440
+ # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
441
+ # # => ActiveRecord::Relation
442
+ # # WITH posts_with_tags_or_comments AS (
443
+ # # (SELECT * FROM posts WHERE (tags_count > 0))
444
+ # # UNION ALL
445
+ # # (SELECT * FROM posts WHERE (comments_count > 0))
446
+ # # )
447
+ # # SELECT * FROM posts
448
+ #
422
449
  # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
423
450
  #
424
451
  # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
@@ -457,6 +484,7 @@ module ActiveRecord
457
484
  # .with(posts_with_comments: Post.where("comments_count > ?", 0))
458
485
  # .with(posts_with_tags: Post.where("tags_count > ?", 0))
459
486
  def with(*args)
487
+ raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
460
488
  check_if_method_has_arguments!(__callee__, args)
461
489
  spawn.with!(*args)
462
490
  end
@@ -467,6 +495,30 @@ module ActiveRecord
467
495
  self
468
496
  end
469
497
 
498
+ # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
499
+ #
500
+ # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
501
+ # # => ActiveRecord::Relation
502
+ # # WITH post_and_replies AS (
503
+ # # (SELECT * FROM posts WHERE id = 42)
504
+ # # UNION ALL
505
+ # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
506
+ # # )
507
+ # # SELECT * FROM posts
508
+ #
509
+ # See `#with` for more information.
510
+ def with_recursive(*args)
511
+ check_if_method_has_arguments!(__callee__, args)
512
+ spawn.with_recursive!(*args)
513
+ end
514
+
515
+ # Like #with_recursive but modifies the relation in place.
516
+ def with_recursive!(*args) # :nodoc:
517
+ self.with_values += args
518
+ @with_is_recursive = true
519
+ self
520
+ end
521
+
470
522
  # Allows you to change a previously set select statement.
471
523
  #
472
524
  # Post.select(:title, :body)
@@ -606,7 +658,8 @@ module ActiveRecord
606
658
  self
607
659
  end
608
660
 
609
- # Allows to specify an order by a specific set of values.
661
+ # Applies an <tt>ORDER BY</tt> clause based on a given +column+,
662
+ # ordered and filtered by a specific set of +values+.
610
663
  #
611
664
  # User.in_order_of(:id, [1, 5, 3])
612
665
  # # SELECT "users".* FROM "users"
@@ -617,8 +670,34 @@ module ActiveRecord
617
670
  # # WHEN "users"."id" = 3 THEN 3
618
671
  # # END ASC
619
672
  #
673
+ # +column+ can point to an enum column; the actual query generated may be different depending
674
+ # on the database adapter and the column definition.
675
+ #
676
+ # class Conversation < ActiveRecord::Base
677
+ # enum :status, [ :active, :archived ]
678
+ # end
679
+ #
680
+ # Conversation.in_order_of(:status, [:archived, :active])
681
+ # # SELECT "conversations".* FROM "conversations"
682
+ # # WHERE "conversations"."status" IN (1, 0)
683
+ # # ORDER BY CASE
684
+ # # WHEN "conversations"."status" = 1 THEN 1
685
+ # # WHEN "conversations"."status" = 0 THEN 2
686
+ # # END ASC
687
+ #
688
+ # +values+ can also include +nil+.
689
+ #
690
+ # Conversation.in_order_of(:status, [nil, :archived, :active])
691
+ # # SELECT "conversations".* FROM "conversations"
692
+ # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
693
+ # # ORDER BY CASE
694
+ # # WHEN "conversations"."status" IS NULL THEN 1
695
+ # # WHEN "conversations"."status" = 1 THEN 2
696
+ # # WHEN "conversations"."status" = 0 THEN 3
697
+ # # END ASC
698
+ #
620
699
  def in_order_of(column, values)
621
- klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
700
+ klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
622
701
  return spawn.none! if values.empty?
623
702
 
624
703
  references = column_references([column])
@@ -1082,7 +1161,7 @@ module ActiveRecord
1082
1161
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
1083
1162
  end
1084
1163
 
1085
- self.where_clause = self.where_clause.or(other.where_clause)
1164
+ self.where_clause = where_clause.or(other.where_clause)
1086
1165
  self.having_clause = having_clause.or(other.having_clause)
1087
1166
  self.references_values |= other.references_values
1088
1167
 
@@ -1453,6 +1532,9 @@ module ActiveRecord
1453
1532
  # Post.excluding(post_one, post_two)
1454
1533
  # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
1455
1534
  #
1535
+ # Post.excluding(Post.drafts)
1536
+ # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
1537
+ #
1456
1538
  # This can also be called on associations. As with the above example, either
1457
1539
  # a single record of collection thereof may be specified:
1458
1540
  #
@@ -1468,14 +1550,15 @@ module ActiveRecord
1468
1550
  # is passed in) are not instances of the same model that the relation is
1469
1551
  # scoping.
1470
1552
  def excluding(*records)
1553
+ relations = records.extract! { |element| element.is_a?(Relation) }
1471
1554
  records.flatten!(1)
1472
1555
  records.compact!
1473
1556
 
1474
- unless records.all?(klass)
1557
+ unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1475
1558
  raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1476
1559
  end
1477
1560
 
1478
- spawn.excluding!(records)
1561
+ spawn.excluding!(records + relations.flat_map(&:ids))
1479
1562
  end
1480
1563
  alias :without :excluding
1481
1564
 
@@ -1487,7 +1570,7 @@ module ActiveRecord
1487
1570
 
1488
1571
  # Returns the Arel object associated with the relation.
1489
1572
  def arel(aliases = nil) # :nodoc:
1490
- @arel ||= build_arel(aliases)
1573
+ @arel ||= with_connection { |c| build_arel(c, aliases) }
1491
1574
  end
1492
1575
 
1493
1576
  def construct_join_dependency(associations, join_type) # :nodoc:
@@ -1508,9 +1591,21 @@ module ActiveRecord
1508
1591
  def build_where_clause(opts, rest = []) # :nodoc:
1509
1592
  opts = sanitize_forbidden_attributes(opts)
1510
1593
 
1594
+ if opts.is_a?(Array)
1595
+ opts, *rest = opts
1596
+ end
1597
+
1511
1598
  case opts
1512
- when String, Array
1513
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1599
+ when String
1600
+ if rest.empty?
1601
+ parts = [Arel.sql(opts)]
1602
+ elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
1603
+ parts = [build_named_bound_sql_literal(opts, rest.first)]
1604
+ elsif opts.include?("?")
1605
+ parts = [build_bound_sql_literal(opts, rest)]
1606
+ else
1607
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1608
+ end
1514
1609
  when Hash
1515
1610
  opts = opts.transform_keys do |key|
1516
1611
  if key.is_a?(Array)
@@ -1546,6 +1641,46 @@ module ActiveRecord
1546
1641
  spawn.async!
1547
1642
  end
1548
1643
 
1644
+ def build_named_bound_sql_literal(statement, values)
1645
+ bound_values = values.transform_values do |value|
1646
+ if ActiveRecord::Relation === value
1647
+ Arel.sql(value.to_sql)
1648
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1649
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1650
+ values.empty? ? nil : values
1651
+ else
1652
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1653
+ value
1654
+ end
1655
+ end
1656
+
1657
+ begin
1658
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
1659
+ rescue Arel::BindError => error
1660
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1661
+ end
1662
+ end
1663
+
1664
+ def build_bound_sql_literal(statement, values)
1665
+ bound_values = values.map do |value|
1666
+ if ActiveRecord::Relation === value
1667
+ Arel.sql(value.to_sql)
1668
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
1669
+ values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
1670
+ values.empty? ? nil : values
1671
+ else
1672
+ value = value.id_for_database if value.respond_to?(:id_for_database)
1673
+ value
1674
+ end
1675
+ end
1676
+
1677
+ begin
1678
+ Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
1679
+ rescue Arel::BindError => error
1680
+ raise ActiveRecord::PreparedStatementInvalid, error.message
1681
+ end
1682
+ end
1683
+
1549
1684
  def lookup_table_klass_from_join_dependencies(table_name)
1550
1685
  each_join_dependencies do |join|
1551
1686
  return join.base_klass if table_name == join.table_name
@@ -1571,11 +1706,10 @@ module ActiveRecord
1571
1706
  end
1572
1707
 
1573
1708
  def assert_mutability!
1574
- raise ImmutableRelation if @loaded
1575
- raise ImmutableRelation if defined?(@arel) && @arel
1709
+ raise ImmutableRelation if @loaded || @arel
1576
1710
  end
1577
1711
 
1578
- def build_arel(aliases = nil)
1712
+ def build_arel(connection, aliases = nil)
1579
1713
  arel = Arel::SelectManager.new(table)
1580
1714
 
1581
1715
  build_joins(arel.join_sources, aliases)
@@ -1747,20 +1881,23 @@ module ActiveRecord
1747
1881
  build_with_value_from_hash(with_value)
1748
1882
  end
1749
1883
 
1750
- arel.with(with_statements)
1884
+ @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
1751
1885
  end
1752
1886
 
1753
1887
  def build_with_value_from_hash(hash)
1754
1888
  hash.map do |name, value|
1755
- expression =
1756
- case value
1757
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1758
- when ActiveRecord::Relation then value.arel
1759
- when Arel::SelectManager then value
1760
- else
1761
- raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1762
- end
1763
- Arel::Nodes::TableAlias.new(expression, name)
1889
+ Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
1890
+ end
1891
+ end
1892
+
1893
+ def build_with_expression_from_value(value)
1894
+ case value
1895
+ when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1896
+ when ActiveRecord::Relation then value.arel
1897
+ when Arel::SelectManager then value
1898
+ when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
1899
+ else
1900
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
1764
1901
  end
1765
1902
  end
1766
1903
 
@@ -1777,12 +1914,14 @@ module ActiveRecord
1777
1914
  case field
1778
1915
  when Symbol
1779
1916
  arel_column(field.to_s) do |attr_name|
1780
- connection.quote_table_name(attr_name)
1917
+ adapter_class.quote_table_name(attr_name)
1781
1918
  end
1782
1919
  when String
1783
1920
  arel_column(field, &:itself)
1784
1921
  when Proc
1785
1922
  field.call
1923
+ when Hash
1924
+ arel_columns_from_hash(field)
1786
1925
  else
1787
1926
  field
1788
1927
  end
@@ -1807,7 +1946,7 @@ module ActiveRecord
1807
1946
 
1808
1947
  def table_name_matches?(from)
1809
1948
  table_name = Regexp.escape(table.name)
1810
- quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1949
+ quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
1811
1950
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1812
1951
  end
1813
1952
 
@@ -1863,7 +2002,9 @@ module ActiveRecord
1863
2002
  args.each do |arg|
1864
2003
  next unless arg.is_a?(Hash)
1865
2004
  arg.each do |_key, value|
1866
- unless VALID_DIRECTIONS.include?(value)
2005
+ if value.is_a?(Hash)
2006
+ validate_order_args([value])
2007
+ elsif VALID_DIRECTIONS.exclude?(value)
1867
2008
  raise ArgumentError,
1868
2009
  "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
1869
2010
  end
@@ -1871,10 +2012,14 @@ module ActiveRecord
1871
2012
  end
1872
2013
  end
1873
2014
 
2015
+ def flattened_args(args)
2016
+ args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
2017
+ end
2018
+
1874
2019
  def preprocess_order_args(order_args)
1875
2020
  @klass.disallow_raw_sql!(
1876
- order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1877
- permit: connection.column_name_with_order_matcher
2021
+ flattened_args(order_args),
2022
+ permit: model.adapter_class.column_name_with_order_matcher
1878
2023
  )
1879
2024
 
1880
2025
  validate_order_args(order_args)
@@ -1888,14 +2033,20 @@ module ActiveRecord
1888
2033
  when Symbol
1889
2034
  order_column(arg.to_s).asc
1890
2035
  when Hash
1891
- arg.map { |field, dir|
1892
- case field
1893
- when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
1894
- field.public_send(dir.downcase)
2036
+ arg.map do |key, value|
2037
+ if value.is_a?(Hash)
2038
+ value.map do |field, dir|
2039
+ order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
2040
+ end
1895
2041
  else
1896
- order_column(field.to_s).public_send(dir.downcase)
2042
+ case key
2043
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
2044
+ key.public_send(value.downcase)
2045
+ else
2046
+ order_column(key.to_s).public_send(value.downcase)
2047
+ end
1897
2048
  end
1898
- }
2049
+ end
1899
2050
  else
1900
2051
  arg
1901
2052
  end
@@ -1912,29 +2063,17 @@ module ActiveRecord
1912
2063
  order_args.flat_map do |arg|
1913
2064
  case arg
1914
2065
  when String, Symbol
1915
- extract_table_name_from(arg)
1916
- when Hash
1917
2066
  arg
1918
- .map do |key, value|
1919
- case value
1920
- when Hash
1921
- key.to_s
1922
- else
1923
- extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
1924
- end
1925
- end
1926
- when Arel::Attribute
1927
- arg.relation.name
1928
- when Arel::Nodes::Ordering
1929
- if arg.expr.is_a?(Arel::Attribute)
1930
- arg.expr.relation.name
1931
- end
2067
+ when Hash
2068
+ arg.keys.select { |e| e.is_a?(String) || e.is_a?(Symbol) }
1932
2069
  end
1933
- end.compact
1934
- end
1935
-
1936
- def extract_table_name_from(string)
1937
- string.match(/^\W?(\w+)\W?\./) && $1
2070
+ end.filter_map do |arg|
2071
+ arg =~ /^\W?(\w+)\W?\./ && $1
2072
+ end +
2073
+ order_args
2074
+ .select { |e| e.is_a?(Hash) }
2075
+ .flat_map { |e| e.map { |k, v| k if v.is_a?(Hash) } }
2076
+ .compact
1938
2077
  end
1939
2078
 
1940
2079
  def order_column(field)
@@ -1942,7 +2081,7 @@ module ActiveRecord
1942
2081
  if attr_name == "count" && !group_values.empty?
1943
2082
  table[attr_name]
1944
2083
  else
1945
- Arel.sql(connection.quote_table_name(attr_name))
2084
+ Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
1946
2085
  end
1947
2086
  end
1948
2087
  end
@@ -2010,14 +2149,14 @@ module ActiveRecord
2010
2149
  def process_select_args(fields)
2011
2150
  fields.flat_map do |field|
2012
2151
  if field.is_a?(Hash)
2013
- transform_select_hash_values(field)
2152
+ arel_columns_from_hash(field)
2014
2153
  else
2015
2154
  field
2016
2155
  end
2017
2156
  end
2018
2157
  end
2019
2158
 
2020
- def transform_select_hash_values(fields)
2159
+ def arel_columns_from_hash(fields)
2021
2160
  fields.flat_map do |key, columns_aliases|
2022
2161
  case columns_aliases
2023
2162
  when Hash
@@ -3,6 +3,9 @@
3
3
  module ActiveRecord
4
4
  class Relation
5
5
  module RecordFetchWarning
6
+ # Deprecated: subscribe to sql.active_record notifications and
7
+ # access the row count field to detect large result set sizes.
8
+ #
6
9
  # When this module is prepended to ActiveRecord::Relation and
7
10
  # +config.active_record.warn_on_records_fetched_greater_than+ is
8
11
  # set to an integer, if the number of records a query returns is
@@ -41,26 +41,10 @@ module ActiveRecord
41
41
  end
42
42
 
43
43
  def merge!(other, *rest) # :nodoc:
44
- options = rest.extract_options!
45
-
46
- if options.key?(:rewhere)
47
- if options[:rewhere]
48
- ActiveRecord.deprecator.warn(<<-MSG.squish)
49
- Specifying `Relation#merge(rewhere: true)` is deprecated, as that has now been
50
- the default since Rails 7.0. Setting the rewhere option will error in Rails 7.2
51
- MSG
52
- else
53
- ActiveRecord.deprecator.warn(<<-MSG.squish)
54
- `Relation#merge(rewhere: false)` is deprecated without replacement,
55
- and will be removed in Rails 7.2
56
- MSG
57
- end
58
- end
59
-
60
44
  if other.is_a?(Hash)
61
- Relation::HashMerger.new(self, other, options[:rewhere]).merge
45
+ Relation::HashMerger.new(self, other).merge
62
46
  elsif other.is_a?(Relation)
63
- Relation::Merger.new(self, other, options[:rewhere]).merge
47
+ Relation::Merger.new(self, other).merge
64
48
  elsif other.respond_to?(:to_proc)
65
49
  instance_exec(&other)
66
50
  else
@@ -23,12 +23,8 @@ module ActiveRecord
23
23
  WhereClause.new(predicates | other.predicates)
24
24
  end
25
25
 
26
- def merge(other, rewhere = nil)
27
- predicates = if rewhere
28
- except_predicates(other.extract_attributes)
29
- else
30
- predicates_unreferenced_by(other)
31
- end
26
+ def merge(other)
27
+ predicates = except_predicates(other.extract_attributes)
32
28
 
33
29
  WhereClause.new(predicates | other.predicates)
34
30
  end
@@ -51,7 +47,11 @@ module ActiveRecord
51
47
  right = right.ast
52
48
  right = right.expr if right.is_a?(Arel::Nodes::Grouping)
53
49
 
54
- or_clause = Arel::Nodes::Or.new(left, right)
50
+ or_clause = if left.is_a?(Arel::Nodes::Or)
51
+ Arel::Nodes::Or.new(left.children + [right])
52
+ else
53
+ Arel::Nodes::Or.new([left, right])
54
+ end
55
55
 
56
56
  common.predicates << Arel::Nodes::Grouping.new(or_clause)
57
57
  common
@@ -156,18 +156,6 @@ module ActiveRecord
156
156
  equalities
157
157
  end
158
158
 
159
- def predicates_unreferenced_by(other)
160
- referenced_columns = other.referenced_columns
161
-
162
- predicates.reject do |node|
163
- attr = extract_attribute(node) || begin
164
- node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
165
- end
166
-
167
- attr && referenced_columns[attr]
168
- end
169
- end
170
-
171
159
  def equality_node?(node)
172
160
  !node.is_a?(String) && node.equality?
173
161
  end