activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -24,22 +24,22 @@ module ActiveRecord
24
24
  # TravelRoute.primary_key = [:origin, :destination]
25
25
  #
26
26
  # TravelRoute.find(["Ottawa", "London"])
27
- # => #<TravelRoute origin: "Ottawa", destination: "London">
27
+ # # => #<TravelRoute origin: "Ottawa", destination: "London">
28
28
  #
29
29
  # TravelRoute.find([["Paris", "Montreal"]])
30
- # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
30
+ # # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
31
  #
32
32
  # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
- # => [
34
- # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
- # #<TravelRoute origin: "New York", destination: "Portland">
36
- # ]
33
+ # # => [
34
+ # # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # # ]
37
37
  #
38
38
  # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
- # => [
40
- # #<TravelRoute origin: "Berlin", destination: "London">,
41
- # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
- # ]
39
+ # # => [
40
+ # # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # # ]
43
43
  #
44
44
  # NOTE: The returned records are in the same order as the ids you provide.
45
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
@@ -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)
@@ -133,14 +141,14 @@ module ActiveRecord
133
141
  #
134
142
  # Product.where(["price = %?", price]).sole
135
143
  def sole
136
- found, undesired = first(2)
144
+ found, undesired = take(2)
137
145
 
138
146
  if found.nil?
139
147
  raise_record_not_found_exception!
140
- elsif undesired.present?
141
- raise ActiveRecord::SoleRecordExceeded.new(self)
142
- else
148
+ elsif undesired.nil?
143
149
  found
150
+ else
151
+ raise ActiveRecord::SoleRecordExceeded.new(self)
144
152
  end
145
153
  end
146
154
 
@@ -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, "#{model.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.
@@ -377,7 +389,7 @@ module ActiveRecord
377
389
  def include?(record)
378
390
  # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
379
391
  # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
380
- return false unless record.is_a?(klass)
392
+ return false unless record.is_a?(model)
381
393
 
382
394
  if loaded? || offset_value || limit_value || having_clause.any?
383
395
  records.include?(record)
@@ -403,9 +415,9 @@ module ActiveRecord
403
415
  # the expected number of results should be provided in the +expected_size+
404
416
  # argument.
405
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
406
- conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
418
+ conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
407
419
 
408
- name = @klass.name
420
+ name = model.name
409
421
 
410
422
  if ids.nil?
411
423
  error = +"Couldn't find #{name}"
@@ -430,7 +442,7 @@ module ActiveRecord
430
442
  if distinct_value && offset_value
431
443
  relation = except(:order).limit!(1)
432
444
  else
433
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
445
+ relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
434
446
  end
435
447
 
436
448
  case conditions
@@ -460,7 +472,9 @@ module ActiveRecord
460
472
  )
461
473
  )
462
474
  relation = skip_query_cache_if_necessary do
463
- klass.connection.distinct_relation_for_primary_key(relation)
475
+ model.with_connection do |c|
476
+ c.distinct_relation_for_primary_key(relation)
477
+ end
464
478
  end
465
479
  end
466
480
 
@@ -476,9 +490,9 @@ module ActiveRecord
476
490
  end
477
491
 
478
492
  def find_with_ids(*ids)
479
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
493
+ raise UnknownPrimaryKey.new(model) if primary_key.nil?
480
494
 
481
- expects_array = if klass.composite_primary_key?
495
+ expects_array = if model.composite_primary_key?
482
496
  ids.first.first.is_a?(Array)
483
497
  else
484
498
  ids.first.is_a?(Array)
@@ -490,7 +504,7 @@ module ActiveRecord
490
504
 
491
505
  ids = ids.compact.uniq
492
506
 
493
- model_name = @klass.name
507
+ model_name = model.name
494
508
 
495
509
  case ids.size
496
510
  when 0
@@ -512,7 +526,7 @@ module ActiveRecord
512
526
  MSG
513
527
  end
514
528
 
515
- relation = if klass.composite_primary_key?
529
+ relation = if model.composite_primary_key?
516
530
  where(primary_key.zip(id).to_h)
517
531
  else
518
532
  where(primary_key => id)
@@ -560,7 +574,7 @@ module ActiveRecord
560
574
  result = relation.records
561
575
 
562
576
  if result.size == ids.size
563
- result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
577
+ result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
564
578
  else
565
579
  raise_record_not_found_exception!(ids, result.size, ids.size)
566
580
  end
@@ -625,24 +639,40 @@ module ActiveRecord
625
639
  end
626
640
 
627
641
  def ordered_relation
628
- if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
629
- order(_order_columns.map { |column| table[column].asc })
642
+ if order_values.empty?
643
+ if !_order_columns.empty?
644
+ return order(_order_columns.map { |column| table[column].asc })
645
+ end
646
+
647
+ if ActiveRecord.raise_on_missing_required_finder_order_columns
648
+ raise MissingRequiredOrderError, <<~MSG.squish
649
+ Relation has no order values, and #{model} has no order columns to use as a default.
650
+ Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
651
+ the model when no `order `is specified on the relation.
652
+ MSG
653
+ else
654
+ ActiveRecord.deprecator.warn(<<~MSG)
655
+ Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
656
+ and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
657
+ or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
658
+ in Rails 8.2.
659
+ MSG
660
+
661
+ self
662
+ end
630
663
  else
631
664
  self
632
665
  end
633
666
  end
634
667
 
635
668
  def _order_columns
636
- oc = []
669
+ columns = Array(model.implicit_order_column)
637
670
 
638
- oc << implicit_order_column if implicit_order_column
639
- oc << query_constraints_list if query_constraints_list
671
+ return columns.compact if columns.length.positive? && columns.last.nil?
640
672
 
641
- if primary_key && query_constraints_list.nil?
642
- oc << primary_key
643
- end
673
+ columns += Array(model.query_constraints_list || model.primary_key)
644
674
 
645
- oc.flatten.uniq.compact
675
+ columns.uniq.compact
646
676
  end
647
677
  end
648
678
  end
@@ -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.
@@ -25,7 +24,7 @@ module ActiveRecord
25
24
  # the values.
26
25
  def other
27
26
  other = Relation.create(
28
- relation.klass,
27
+ relation.model,
29
28
  table: relation.table,
30
29
  predicate_builder: relation.predicate_builder
31
30
  )
@@ -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 -
@@ -86,7 +84,7 @@ module ActiveRecord
86
84
  def merge_select_values
87
85
  return if other.select_values.empty?
88
86
 
89
- if other.klass == relation.klass
87
+ if other.model == relation.model
90
88
  relation.select_values |= other.select_values
91
89
  else
92
90
  relation.select_values |= other.instance_eval do
@@ -98,12 +96,12 @@ module ActiveRecord
98
96
  def merge_preloads
99
97
  return if other.preload_values.empty? && other.includes_values.empty?
100
98
 
101
- if other.klass == relation.klass
99
+ if other.model == relation.model
102
100
  relation.preload_values |= other.preload_values unless other.preload_values.empty?
103
101
  relation.includes_values |= other.includes_values unless other.includes_values.empty?
104
102
  else
105
- reflection = relation.klass.reflect_on_all_associations.find do |r|
106
- r.class_name == other.klass.name
103
+ reflection = relation.model.reflect_on_all_associations.find do |r|
104
+ r.class_name == other.model.name
107
105
  end || return
108
106
 
109
107
  unless other.preload_values.empty?
@@ -119,7 +117,7 @@ module ActiveRecord
119
117
  def merge_joins
120
118
  return if other.joins_values.empty?
121
119
 
122
- if other.klass == relation.klass
120
+ if other.model == relation.model
123
121
  relation.joins_values |= other.joins_values
124
122
  else
125
123
  associations, others = other.joins_values.partition do |join|
@@ -138,7 +136,7 @@ module ActiveRecord
138
136
  def merge_outer_joins
139
137
  return if other.left_outer_joins_values.empty?
140
138
 
141
- if other.klass == relation.klass
139
+ if other.model == relation.model
142
140
  relation.left_outer_joins_values |= other.left_outer_joins_values
143
141
  else
144
142
  associations, others = other.left_outer_joins_values.partition do |join|
@@ -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)
@@ -187,7 +185,7 @@ module ActiveRecord
187
185
 
188
186
  def replace_from_clause?
189
187
  relation.from_clause.empty? && !other.from_clause.empty? &&
190
- relation.klass.base_class == other.klass.base_class
188
+ relation.model.base_class == other.model.base_class
191
189
  end
192
190
  end
193
191
  end
@@ -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
 
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class AssociationQueryValue # :nodoc:
6
- def initialize(associated_table, value)
7
- @associated_table = associated_table
6
+ def initialize(reflection, value)
7
+ @reflection = reflection
8
8
  @value = value
9
9
  end
10
10
 
11
11
  def queries
12
- if associated_table.join_foreign_key.is_a?(Array)
12
+ if reflection.join_foreign_key.is_a?(Array)
13
13
  id_list = ids
14
14
  id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
15
 
16
- id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
16
+ id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
17
17
  else
18
- [ associated_table.join_foreign_key => ids ]
18
+ [ reflection.join_foreign_key => ids ]
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :value
23
+ attr_reader :reflection, :value
24
24
 
25
25
  def ids
26
26
  case value
@@ -37,15 +37,15 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  def primary_key
40
- associated_table.join_primary_key
40
+ reflection.join_primary_key
41
41
  end
42
42
 
43
43
  def primary_type
44
- associated_table.join_primary_type
44
+ reflection.join_primary_type
45
45
  end
46
46
 
47
47
  def polymorphic_name
48
- associated_table.polymorphic_name_association
48
+ reflection.polymorphic_name
49
49
  end
50
50
 
51
51
  def select_clause?
@@ -57,9 +57,17 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def convert_to_id(value)
60
- return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
60
+ if primary_key.is_a?(Array)
61
+ primary_key.map do |attribute|
62
+ next nil if value.nil?
61
63
 
62
- if value.respond_to?(primary_key)
64
+ if attribute == "id"
65
+ value.id_value
66
+ else
67
+ value.public_send(attribute)
68
+ end
69
+ end
70
+ elsif value.respond_to?(primary_key)
63
71
  value.public_send(primary_key)
64
72
  else
65
73
  value
@@ -3,24 +3,24 @@
3
3
  module ActiveRecord
4
4
  class PredicateBuilder
5
5
  class PolymorphicArrayValue # :nodoc:
6
- def initialize(associated_table, values)
7
- @associated_table = associated_table
6
+ def initialize(reflection, values)
7
+ @reflection = reflection
8
8
  @values = values
9
9
  end
10
10
 
11
11
  def queries
12
- return [ associated_table.join_foreign_key => values ] if values.empty?
12
+ return [ reflection.join_foreign_key => values ] if values.empty?
13
13
 
14
14
  type_to_ids_mapping.map do |type, ids|
15
15
  query = {}
16
- query[associated_table.join_foreign_type] = type if type
17
- query[associated_table.join_foreign_key] = ids
16
+ query[reflection.join_foreign_type] = type if type
17
+ query[reflection.join_foreign_key] = ids
18
18
  query
19
19
  end
20
20
  end
21
21
 
22
22
  private
23
- attr_reader :associated_table, :values
23
+ attr_reader :reflection, :values
24
24
 
25
25
  def type_to_ids_mapping
26
26
  default_hash = Hash.new { |hsh, key| hsh[key] = [] }
@@ -30,14 +30,14 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def primary_key(value)
33
- associated_table.join_primary_key(klass(value))
33
+ reflection.join_primary_key(klass(value))
34
34
  end
35
35
 
36
36
  def klass(value)
37
37
  if value.is_a?(Base)
38
38
  value.class
39
39
  elsif value.is_a?(Relation)
40
- value.klass
40
+ value.model
41
41
  end
42
42
  end
43
43
 
@@ -9,10 +9,11 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- if value.klass.composite_primary_key?
13
- raise ArgumentError, "Cannot map composite primary key #{value.klass.primary_key} to #{attribute.name}"
12
+ model = value.model
13
+ if model.composite_primary_key?
14
+ raise ArgumentError, "Cannot map composite primary key #{model.primary_key} to #{attribute.name}"
14
15
  else
15
- value = value.select(value.table[value.klass.primary_key])
16
+ value = value.select(value.table[model.primary_key])
16
17
  end
17
18
  end
18
19
 
@@ -28,16 +28,16 @@ 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
37
37
 
38
38
  # Define how a class is converted to Arel nodes when passed to +where+.
39
39
  # The handler can be any object that responds to +call+, and will be used
40
- # for any value that +===+ the class given. For example:
40
+ # for any value that <tt>===</tt> the class given. For example:
41
41
  #
42
42
  # MyCustomDateRange = Struct.new(:start, :end)
43
43
  # handler = proc do |column, range|
@@ -72,11 +72,24 @@ module ActiveRecord
72
72
  table.associated_table(table_name, &block).arel_table[column_name]
73
73
  end
74
74
 
75
+ def with(table)
76
+ other = dup
77
+ other.table = table
78
+ other
79
+ end
80
+
75
81
  protected
82
+ attr_writer :table
83
+
76
84
  def expand_from_hash(attributes, &block)
77
- return ["1=0"] if attributes.empty?
85
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
78
86
 
79
87
  attributes.flat_map do |key, value|
88
+ if key.is_a?(Array) && key.size == 1
89
+ key = key.first
90
+ value = value.flatten
91
+ end
92
+
80
93
  if key.is_a?(Array)
81
94
  queries = Array(value).map do |ids_set|
82
95
  raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
@@ -86,24 +99,26 @@ module ActiveRecord
86
99
  elsif value.is_a?(Hash) && !table.has_column?(key)
87
100
  table.associated_table(key, &block)
88
101
  .predicate_builder.expand_from_hash(value.stringify_keys)
89
- elsif table.associated_with?(key)
102
+ elsif (associated_reflection = table.associated_with(key))
90
103
  # Find the foreign key when using queries such as:
91
104
  # Post.where(author: author)
92
105
  #
93
106
  # For polymorphic relationships, find the foreign key and type:
94
107
  # PriceEstimate.where(estimate_of: treasure)
95
- associated_table = table.associated_table(key)
96
- if associated_table.polymorphic_association?
108
+
109
+ if associated_reflection.polymorphic?
97
110
  value = [value] unless value.is_a?(Array)
98
111
  klass = PolymorphicArrayValue
99
- elsif associated_table.through_association?
112
+ elsif associated_reflection.through_reflection?
113
+ associated_table = table.associated_table(key)
114
+
100
115
  next associated_table.predicate_builder.expand_from_hash(
101
116
  associated_table.primary_key => value
102
117
  )
103
118
  end
104
119
 
105
120
  klass ||= AssociationQueryValue
106
- queries = klass.new(associated_table, value).queries.map! do |query|
121
+ queries = klass.new(associated_reflection, value).queries.map! do |query|
107
122
  # If the query produced is identical to attributes don't go any deeper.
108
123
  # Prevents stack level too deep errors when association and foreign_key are identical.
109
124
  query == attributes ? self[key, value] : expand_from_hash(query)
@@ -142,7 +157,7 @@ module ActiveRecord
142
157
  queries.first
143
158
  else
144
159
  queries.map! { |query| query.reduce(&:and) }
145
- queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
160
+ queries = Arel::Nodes::Or.new(queries)
146
161
  Arel::Nodes::Grouping.new(queries)
147
162
  end
148
163
  end
@@ -15,7 +15,9 @@ module ActiveRecord
15
15
  elsif @type.serialized?
16
16
  value_for_database
17
17
  elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
- @value_before_type_cast = @value_before_type_cast.deep_dup
18
+ unless @value_before_type_cast.frozen?
19
+ @value_before_type_cast = @value_before_type_cast.deep_dup
20
+ end
19
21
  end
20
22
  end
21
23
 
@@ -35,7 +37,7 @@ module ActiveRecord
35
37
  def nil?
36
38
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
37
39
  value_before_type_cast.nil? ||
38
- type.respond_to?(:subtype) && serializable? && value_for_database.nil?
40
+ (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
39
41
  end
40
42
  end
41
43