activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -92,10 +92,11 @@ module ActiveRecord
92
92
  @scope.joins!(association)
93
93
  end
94
94
 
95
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
95
96
  if reflection.options[:class_name]
96
- self.not(association => { reflection.association_primary_key => nil })
97
+ self.not(association => association_conditions)
97
98
  else
98
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
99
+ self.not(reflection.table_name => association_conditions)
99
100
  end
100
101
  end
101
102
 
@@ -124,10 +125,11 @@ module ActiveRecord
124
125
  associations.each do |association|
125
126
  reflection = scope_association_reflection(association)
126
127
  @scope.left_outer_joins!(association)
128
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
127
129
  if reflection.options[:class_name]
128
- @scope.where!(association => { reflection.association_primary_key => nil })
130
+ @scope.where!(association => association_conditions)
129
131
  else
130
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
132
+ @scope.where!(reflection.table_name => association_conditions)
131
133
  end
132
134
  end
133
135
 
@@ -136,9 +138,10 @@ module ActiveRecord
136
138
 
137
139
  private
138
140
  def scope_association_reflection(association)
139
- reflection = @scope.klass._reflect_on_association(association)
141
+ model = @scope.model
142
+ reflection = model._reflect_on_association(association)
140
143
  unless reflection
141
- raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
144
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
142
145
  end
143
146
  reflection
144
147
  end
@@ -254,6 +257,10 @@ module ActiveRecord
254
257
  self
255
258
  end
256
259
 
260
+ def all # :nodoc:
261
+ spawn
262
+ end
263
+
257
264
  # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
258
265
  # Performs a single query joining all specified associations. For example:
259
266
  #
@@ -491,7 +498,8 @@ module ActiveRecord
491
498
 
492
499
  # Like #with, but modifies relation in place.
493
500
  def with!(*args) # :nodoc:
494
- self.with_values += args
501
+ args = process_with_args(args)
502
+ self.with_values |= args
495
503
  self
496
504
  end
497
505
 
@@ -499,10 +507,10 @@ module ActiveRecord
499
507
  #
500
508
  # 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
509
  # # => ActiveRecord::Relation
502
- # # WITH post_and_replies AS (
510
+ # # WITH RECURSIVE post_and_replies AS (
503
511
  # # (SELECT * FROM posts WHERE id = 42)
504
512
  # # UNION ALL
505
- # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
513
+ # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
506
514
  # # )
507
515
  # # SELECT * FROM posts
508
516
  #
@@ -514,7 +522,8 @@ module ActiveRecord
514
522
 
515
523
  # Like #with_recursive but modifies the relation in place.
516
524
  def with_recursive!(*args) # :nodoc:
517
- self.with_values += args
525
+ args = process_with_args(args)
526
+ self.with_values |= args
518
527
  @with_is_recursive = true
519
528
  self
520
529
  end
@@ -567,7 +576,7 @@ module ActiveRecord
567
576
  end
568
577
 
569
578
  def group!(*args) # :nodoc:
570
- self.group_values += args
579
+ self.group_values |= args
571
580
  self
572
581
  end
573
582
 
@@ -696,26 +705,39 @@ module ActiveRecord
696
705
  # # WHEN "conversations"."status" = 0 THEN 3
697
706
  # # END ASC
698
707
  #
699
- def in_order_of(column, values)
700
- klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
708
+ # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
709
+ #
710
+ # Conversation.in_order_of(:status, [:archived, :active], filter: false)
711
+ # # SELECT "conversations".* FROM "conversations"
712
+ # # ORDER BY CASE
713
+ # # WHEN "conversations"."status" = 1 THEN 1
714
+ # # WHEN "conversations"."status" = 0 THEN 2
715
+ # # ELSE 3
716
+ # # END ASC
717
+ def in_order_of(column, values, filter: true)
718
+ model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
701
719
  return spawn.none! if values.empty?
702
720
 
703
721
  references = column_references([column])
704
722
  self.references_values |= references unless references.empty?
705
723
 
706
- values = values.map { |value| type_caster.type_cast_for_database(column, value) }
724
+ values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
707
725
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
708
726
 
709
- where_clause =
710
- if values.include?(nil)
711
- arel_column.in(values.compact).or(arel_column.eq(nil))
712
- else
713
- arel_column.in(values)
714
- end
727
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
715
728
 
716
- spawn
717
- .order!(build_case_for_value_position(arel_column, values))
718
- .where!(where_clause)
729
+ if filter
730
+ where_clause =
731
+ if values.include?(nil)
732
+ arel_column.in(values.compact).or(arel_column.eq(nil))
733
+ else
734
+ arel_column.in(values)
735
+ end
736
+
737
+ scope = scope.where!(where_clause)
738
+ end
739
+
740
+ scope
719
741
  end
720
742
 
721
743
  # Replaces any existing order defined on the relation with the specified order.
@@ -787,7 +809,7 @@ module ActiveRecord
787
809
  end
788
810
 
789
811
  def unscope!(*args) # :nodoc:
790
- self.unscope_values += args
812
+ self.unscope_values |= args
791
813
 
792
814
  args.each do |scope|
793
815
  case scope
@@ -1191,6 +1213,7 @@ module ActiveRecord
1191
1213
  end
1192
1214
 
1193
1215
  def limit!(value) # :nodoc:
1216
+ value = Integer(value) unless value.nil?
1194
1217
  self.limit_value = value
1195
1218
  self
1196
1219
  end
@@ -1277,13 +1300,13 @@ module ActiveRecord
1277
1300
  #
1278
1301
  # users = User.readonly
1279
1302
  # users.first.save
1280
- # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1303
+ # # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
1281
1304
  #
1282
1305
  # To make a readonly relation writable, pass +false+.
1283
1306
  #
1284
1307
  # users.readonly(false)
1285
1308
  # users.first.save
1286
- # => true
1309
+ # # => true
1287
1310
  def readonly(value = true)
1288
1311
  spawn.readonly!(value)
1289
1312
  end
@@ -1298,7 +1321,7 @@ module ActiveRecord
1298
1321
  #
1299
1322
  # user = User.strict_loading.first
1300
1323
  # user.comments.to_a
1301
- # => ActiveRecord::StrictLoadingViolationError
1324
+ # # => ActiveRecord::StrictLoadingViolationError
1302
1325
  def strict_loading(value = true)
1303
1326
  spawn.strict_loading!(value)
1304
1327
  end
@@ -1443,7 +1466,7 @@ module ActiveRecord
1443
1466
  modules << Module.new(&block) if block
1444
1467
  modules.flatten!
1445
1468
 
1446
- self.extending_values += modules
1469
+ self.extending_values |= modules
1447
1470
  extend(*extending_values) if extending_values.any?
1448
1471
 
1449
1472
  self
@@ -1511,7 +1534,7 @@ module ActiveRecord
1511
1534
 
1512
1535
  # Like #annotate, but modifies relation in place.
1513
1536
  def annotate!(*args) # :nodoc:
1514
- self.annotate_values += args
1537
+ self.annotate_values |= args
1515
1538
  self
1516
1539
  end
1517
1540
 
@@ -1554,8 +1577,8 @@ module ActiveRecord
1554
1577
  records.flatten!(1)
1555
1578
  records.compact!
1556
1579
 
1557
- unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1558
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1580
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1581
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1559
1582
  end
1560
1583
 
1561
1584
  spawn.excluding!(records + relations.flat_map(&:ids))
@@ -1570,12 +1593,12 @@ module ActiveRecord
1570
1593
 
1571
1594
  # Returns the Arel object associated with the relation.
1572
1595
  def arel(aliases = nil) # :nodoc:
1573
- @arel ||= with_connection { |c| build_arel(c, aliases) }
1596
+ @arel ||= build_arel(aliases)
1574
1597
  end
1575
1598
 
1576
1599
  def construct_join_dependency(associations, join_type) # :nodoc:
1577
1600
  ActiveRecord::Associations::JoinDependency.new(
1578
- klass, table, associations, join_type
1601
+ model, table, associations, join_type
1579
1602
  )
1580
1603
  end
1581
1604
 
@@ -1604,15 +1627,15 @@ module ActiveRecord
1604
1627
  elsif opts.include?("?")
1605
1628
  parts = [build_bound_sql_literal(opts, rest)]
1606
1629
  else
1607
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1630
+ parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
1608
1631
  end
1609
1632
  when Hash
1610
1633
  opts = opts.transform_keys do |key|
1611
1634
  if key.is_a?(Array)
1612
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1635
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1613
1636
  else
1614
1637
  key = key.to_s
1615
- klass.attribute_aliases[key] || key
1638
+ model.attribute_aliases[key] || key
1616
1639
  end
1617
1640
  end
1618
1641
  references = PredicateBuilder.references(opts)
@@ -1631,21 +1654,16 @@ module ActiveRecord
1631
1654
  end
1632
1655
  alias :build_having_clause :build_where_clause
1633
1656
 
1634
- def async!
1657
+ def async! # :nodoc:
1635
1658
  @async = true
1636
1659
  self
1637
1660
  end
1638
1661
 
1639
- protected
1640
- def arel_columns(columns)
1662
+ def arel_columns(columns) # :nodoc:
1641
1663
  columns.flat_map do |field|
1642
1664
  case field
1643
- when Symbol
1644
- arel_column(field.to_s) do |attr_name|
1645
- adapter_class.quote_table_name(attr_name)
1646
- end
1647
- when String
1648
- arel_column(field, &:itself)
1665
+ when Symbol, String
1666
+ arel_column(field)
1649
1667
  when Proc
1650
1668
  field.call
1651
1669
  when Hash
@@ -1729,32 +1747,27 @@ module ActiveRecord
1729
1747
  raise UnmodifiableRelation if @loaded || @arel
1730
1748
  end
1731
1749
 
1732
- def build_arel(connection, aliases = nil)
1750
+ def build_arel(aliases)
1733
1751
  arel = Arel::SelectManager.new(table)
1734
1752
 
1735
1753
  build_joins(arel.join_sources, aliases)
1736
1754
 
1737
1755
  arel.where(where_clause.ast) unless where_clause.empty?
1738
1756
  arel.having(having_clause.ast) unless having_clause.empty?
1739
- arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1757
+ arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
1740
1758
  arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1741
- arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1759
+ arel.group(*arel_columns(group_values)) unless group_values.empty?
1742
1760
 
1743
1761
  build_order(arel)
1744
1762
  build_with(arel)
1745
1763
  build_select(arel)
1746
1764
 
1747
1765
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1766
+ arel.comment(*annotate_values) unless annotate_values.empty?
1748
1767
  arel.distinct(distinct_value)
1749
1768
  arel.from(build_from) unless from_clause.empty?
1750
1769
  arel.lock(lock_value) if lock_value
1751
1770
 
1752
- unless annotate_values.empty?
1753
- annotates = annotate_values
1754
- annotates = annotates.uniq if annotates.size > 1
1755
- arel.comment(*annotates)
1756
- end
1757
-
1758
1771
  arel
1759
1772
  end
1760
1773
 
@@ -1828,7 +1841,7 @@ module ActiveRecord
1828
1841
 
1829
1842
  joins = joins_values.dup
1830
1843
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1831
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1844
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1832
1845
  end
1833
1846
 
1834
1847
  joins.each_with_index do |join, i|
@@ -1885,8 +1898,8 @@ module ActiveRecord
1885
1898
  def build_select(arel)
1886
1899
  if select_values.any?
1887
1900
  arel.project(*arel_columns(select_values))
1888
- elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1889
- arel.project(*klass.column_names.map { |field| table[field] })
1901
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1902
+ arel.project(*model.column_names.map { |field| table[field] })
1890
1903
  else
1891
1904
  arel.project(table[Arel.star])
1892
1905
  end
@@ -1896,8 +1909,6 @@ module ActiveRecord
1896
1909
  return if with_values.empty?
1897
1910
 
1898
1911
  with_statements = with_values.map do |with_value|
1899
- raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
1900
-
1901
1912
  build_with_value_from_hash(with_value)
1902
1913
  end
1903
1914
 
@@ -1912,7 +1923,8 @@ module ActiveRecord
1912
1923
 
1913
1924
  def build_with_expression_from_value(value, nested = false)
1914
1925
  case value
1915
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1926
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1927
+ Arel::Nodes::Grouping.new(value)
1916
1928
  when ActiveRecord::Relation
1917
1929
  if nested
1918
1930
  value.arel.ast
@@ -1939,37 +1951,76 @@ module ActiveRecord
1939
1951
  with_table = Arel::Table.new(name)
1940
1952
 
1941
1953
  table.join(with_table, kind).on(
1942
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1954
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1943
1955
  ).join_sources.first
1944
1956
  end
1945
1957
 
1958
+ def arel_columns_from_hash(fields)
1959
+ fields.flat_map do |table_name, columns|
1960
+ table_name = table_name.name if table_name.is_a?(Symbol)
1961
+ case columns
1962
+ when Symbol, String
1963
+ arel_column_with_table(table_name, columns)
1964
+ when Array
1965
+ columns.map do |column|
1966
+ arel_column_with_table(table_name, column)
1967
+ end
1968
+ else
1969
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1970
+ end
1971
+ end
1972
+ end
1973
+
1974
+ def arel_column_with_table(table_name, column_name)
1975
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1976
+
1977
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1978
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1979
+ lookup_table_klass_from_join_dependencies(table_name)
1980
+ end
1981
+ else
1982
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1983
+ end
1984
+ end
1985
+
1946
1986
  def arel_column(field)
1947
- field = klass.attribute_aliases[field] || field
1987
+ field = field.name if is_symbol = field.is_a?(Symbol)
1988
+
1989
+ field = model.attribute_aliases[field] || field
1948
1990
  from = from_clause.name || from_clause.value
1949
1991
 
1950
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1992
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1951
1993
  table[field]
1952
1994
  elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
- self.references_values |= [Arel.sql(table, retryable: true)]
1954
- predicate_builder.resolve_arel_attribute(table, column) do
1955
- lookup_table_klass_from_join_dependencies(table)
1956
- end
1957
- else
1995
+ arel_column_with_table(table, column)
1996
+ elsif block_given?
1958
1997
  yield field
1998
+ elsif Arel.arel_node?(field)
1999
+ field
2000
+ elsif is_symbol
2001
+ Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
2002
+ else
2003
+ Arel.sql(field)
1959
2004
  end
1960
2005
  end
1961
2006
 
1962
2007
  def table_name_matches?(from)
1963
2008
  table_name = Regexp.escape(table.name)
1964
- quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
2009
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1965
2010
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1966
2011
  end
1967
2012
 
1968
2013
  def reverse_sql_order(order_query)
1969
2014
  if order_query.empty?
1970
- return [table[primary_key].desc] if primary_key
1971
- raise IrreversibleOrderError,
1972
- "Relation has no current order and table has no primary key to be used as default order"
2015
+ if !_reverse_order_columns.empty?
2016
+ return _reverse_order_columns.map { |column| table[column].desc }
2017
+ end
2018
+
2019
+ raise IrreversibleOrderError, <<~MSG.squish
2020
+ Relation has no order values, and #{model} has no order columns to use as a default.
2021
+ Set at least one of `implicit_order_column`, or `primary_key` on the model when no
2022
+ `order `is specified on the relation.
2023
+ MSG
1973
2024
  end
1974
2025
 
1975
2026
  order_query.flat_map do |o|
@@ -1994,6 +2045,13 @@ module ActiveRecord
1994
2045
  end
1995
2046
  end
1996
2047
 
2048
+ def _reverse_order_columns
2049
+ roc = []
2050
+ roc << model.implicit_order_column if model.implicit_order_column
2051
+ roc << model.primary_key if model.primary_key
2052
+ roc.flatten.uniq.compact
2053
+ end
2054
+
1997
2055
  def does_not_support_reverse?(order)
1998
2056
  # Account for String subclasses like Arel::Nodes::SqlLiteral that
1999
2057
  # override methods like #count.
@@ -2032,7 +2090,7 @@ module ActiveRecord
2032
2090
  end
2033
2091
 
2034
2092
  def preprocess_order_args(order_args)
2035
- @klass.disallow_raw_sql!(
2093
+ model.disallow_raw_sql!(
2036
2094
  flattened_args(order_args),
2037
2095
  permit: model.adapter_class.column_name_with_order_matcher
2038
2096
  )
@@ -2070,7 +2128,7 @@ module ActiveRecord
2070
2128
 
2071
2129
  def sanitize_order_arguments(order_args)
2072
2130
  order_args.map! do |arg|
2073
- klass.sanitize_sql_for_order(arg)
2131
+ model.sanitize_sql_for_order(arg)
2074
2132
  end
2075
2133
  end
2076
2134
 
@@ -2096,7 +2154,7 @@ module ActiveRecord
2096
2154
  arg.expr.relation.name
2097
2155
  end
2098
2156
  end
2099
- end.compact
2157
+ end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
2100
2158
  end
2101
2159
 
2102
2160
  def extract_table_name_from(string)
@@ -2108,17 +2166,18 @@ module ActiveRecord
2108
2166
  if attr_name == "count" && !group_values.empty?
2109
2167
  table[attr_name]
2110
2168
  else
2111
- Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
2169
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
2112
2170
  end
2113
2171
  end
2114
2172
  end
2115
2173
 
2116
- def build_case_for_value_position(column, values)
2174
+ def build_case_for_value_position(column, values, filter: true)
2117
2175
  node = Arel::Nodes::Case.new
2118
2176
  values.each.with_index(1) do |value, order|
2119
2177
  node.when(column.eq(value)).then(order)
2120
2178
  end
2121
2179
 
2180
+ node = node.else(values.length + 1) unless filter
2122
2181
  Arel::Nodes::Ascending.new(node)
2123
2182
  end
2124
2183
 
@@ -2176,38 +2235,40 @@ module ActiveRecord
2176
2235
  def process_select_args(fields)
2177
2236
  fields.flat_map do |field|
2178
2237
  if field.is_a?(Hash)
2179
- arel_columns_from_hash(field)
2238
+ arel_column_aliases_from_hash(field)
2180
2239
  else
2181
2240
  field
2182
2241
  end
2183
2242
  end
2184
2243
  end
2185
2244
 
2186
- def arel_columns_from_hash(fields)
2245
+ def arel_column_aliases_from_hash(fields)
2187
2246
  fields.flat_map do |key, columns_aliases|
2247
+ table_name = key.is_a?(Symbol) ? key.name : key
2188
2248
  case columns_aliases
2189
2249
  when Hash
2190
2250
  columns_aliases.map do |column, column_alias|
2191
- if values[:joins]&.include?(key)
2192
- references = PredicateBuilder.references({ key.to_s => fields[key] })
2193
- self.references_values |= references unless references.empty?
2194
- end
2195
- arel_column("#{key}.#{column}") do
2196
- predicate_builder.resolve_arel_attribute(key.to_s, column)
2197
- end.as(column_alias.to_s)
2251
+ arel_column_with_table(table_name, column)
2252
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2198
2253
  end
2199
2254
  when Array
2200
2255
  columns_aliases.map do |column|
2201
- arel_column("#{key}.#{column}", &:itself)
2256
+ arel_column_with_table(table_name, column)
2202
2257
  end
2203
2258
  when String, Symbol
2204
- arel_column(key.to_s) do
2205
- predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2206
- end.as(columns_aliases.to_s)
2259
+ arel_column(key)
2260
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2207
2261
  end
2208
2262
  end
2209
2263
  end
2210
2264
 
2265
+ def process_with_args(args)
2266
+ args.flat_map do |arg|
2267
+ raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
2268
+ arg.map { |k, v| { k => v } }
2269
+ end
2270
+ end
2271
+
2211
2272
  STRUCTURAL_VALUE_METHODS = (
2212
2273
  Relation::VALUE_METHODS -
2213
2274
  [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
@@ -2217,11 +2278,11 @@ module ActiveRecord
2217
2278
  values = other.values
2218
2279
  STRUCTURAL_VALUE_METHODS.reject do |method|
2219
2280
  v1, v2 = @values[method], values[method]
2220
- if v1.is_a?(Array)
2221
- next true unless v2.is_a?(Array)
2222
- v1 = v1.uniq
2223
- v2 = v2.uniq
2224
- end
2281
+
2282
+ # `and`/`or` are focused to combine where-like clauses, so it relaxes
2283
+ # the difference when other's multi values are uninitialized.
2284
+ next true if v1.is_a?(Array) && v2.nil?
2285
+
2225
2286
  v1 == v2
2226
2287
  end
2227
2288
  end
@@ -7,7 +7,7 @@ require "active_record/relation/merger"
7
7
  module ActiveRecord
8
8
  module SpawnMethods
9
9
  def spawn # :nodoc:
10
- already_in_scope?(klass.scope_registry) ? klass.all : clone
10
+ already_in_scope?(model.scope_registry) ? model.all : clone
11
11
  end
12
12
 
13
13
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
@@ -52,18 +52,18 @@ module ActiveRecord
52
52
  end
53
53
  end
54
54
 
55
- # Removes from the query the condition(s) specified in +skips+.
55
+ # Removes the condition(s) specified in +skips+ from the query.
56
56
  #
57
- # Post.order('id asc').except(:order) # discards the order condition
58
- # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
57
+ # Post.order('id asc').except(:order) # removes the order condition
58
+ # Post.where('id > 10').order('id asc').except(:where) # removes the where condition but keeps the order
59
59
  def except(*skips)
60
60
  relation_with values.except(*skips)
61
61
  end
62
62
 
63
- # Removes any condition from the query other than the one(s) specified in +onlies+.
63
+ # Keeps only the condition(s) specified in +onlies+ in the query, removing all others.
64
64
  #
65
- # Post.order('id asc').only(:where) # discards the order condition
66
- # Post.order('id asc').only(:where, :order) # uses the specified order
65
+ # Post.order('id asc').only(:where) # keeps only the where condition, removes the order
66
+ # Post.order('id asc').only(:where, :order) # keeps only the where and order conditions
67
67
  def only(*onlies)
68
68
  relation_with values.slice(*onlies)
69
69
  end
@@ -135,10 +135,14 @@ module ActiveRecord
135
135
 
136
136
  def extract_attribute(node)
137
137
  attr_node = nil
138
- Arel.fetch_attribute(node) do |attr|
139
- return if attr_node&.!= attr # all attr nodes should be the same
138
+
139
+ valid_attrs = Arel.fetch_attribute(node) do |attr|
140
+ !attr_node || attr_node == attr # all attr nodes should be the same
141
+ ensure
140
142
  attr_node = attr
141
143
  end
144
+ return unless valid_attrs # all nested nodes should yield an attribute
145
+
142
146
  attr_node
143
147
  end
144
148
 
@@ -172,11 +176,13 @@ module ActiveRecord
172
176
  end
173
177
 
174
178
  def except_predicates(columns)
179
+ return predicates if columns.empty?
180
+
175
181
  attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
176
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
177
183
 
178
184
  predicates.reject do |node|
179
- if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
185
+ if !non_attrs.empty? && equality_node?(node) && node.left.is_a?(Arel::Predications)
180
186
  non_attrs.include?(node.left)
181
187
  end || Arel.fetch_attribute(node) do |attr|
182
188
  attrs.include?(attr) || columns.include?(attr.name.to_s)
@@ -188,7 +194,7 @@ module ActiveRecord
188
194
  non_empty_predicates.map do |node|
189
195
  case node
190
196
  when Arel::Nodes::SqlLiteral, ::String
191
- wrap_sql_literal(node)
197
+ Arel::Nodes::Grouping.new(node)
192
198
  else node
193
199
  end
194
200
  end
@@ -199,13 +205,6 @@ module ActiveRecord
199
205
  predicates - ARRAY_WITH_EMPTY_STRING
200
206
  end
201
207
 
202
- def wrap_sql_literal(node)
203
- if ::String === node
204
- node = Arel.sql(node)
205
- end
206
- Arel::Nodes::Grouping.new(node)
207
- end
208
-
209
208
  def extract_node_value(node)
210
209
  if node.respond_to?(:value_before_type_cast)
211
210
  node.value_before_type_cast