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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/associations/nested_error"
4
+
3
5
  module ActiveRecord
4
6
  # = Active Record Autosave Association
5
7
  #
@@ -219,8 +221,10 @@ module ActiveRecord
219
221
  if reflection.validate? && !method_defined?(validation_method)
220
222
  if reflection.collection?
221
223
  method = :validate_collection_association
224
+ elsif reflection.has_one?
225
+ method = :validate_has_one_association
222
226
  else
223
- method = :validate_single_association
227
+ method = :validate_belongs_to_association
224
228
  end
225
229
 
226
230
  define_non_cyclic_method(validation_method) { send(method, reflection) }
@@ -272,6 +276,16 @@ module ActiveRecord
272
276
  new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
273
277
  end
274
278
 
279
+ def validating_belongs_to_for?(association)
280
+ @validating_belongs_to_for ||= {}
281
+ @validating_belongs_to_for[association]
282
+ end
283
+
284
+ def autosaving_belongs_to_for?(association)
285
+ @autosaving_belongs_to_for ||= {}
286
+ @autosaving_belongs_to_for[association]
287
+ end
288
+
275
289
  private
276
290
  def init_internals
277
291
  super
@@ -311,11 +325,33 @@ module ActiveRecord
311
325
  end
312
326
 
313
327
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
314
- # turned on for the association.
315
- def validate_single_association(reflection)
328
+ # turned on for the has_one association.
329
+ def validate_has_one_association(reflection)
316
330
  association = association_instance_get(reflection.name)
317
331
  record = association && association.reader
318
- association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
332
+ return unless record && (record.changed_for_autosave? || custom_validation_context?)
333
+
334
+ inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
335
+ return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
336
+ record.autosaving_belongs_to_for?(inverse_association))
337
+
338
+ association_valid?(association, record)
339
+ end
340
+
341
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
342
+ # turned on for the belongs_to association.
343
+ def validate_belongs_to_association(reflection)
344
+ association = association_instance_get(reflection.name)
345
+ record = association && association.reader
346
+ return unless record && (record.changed_for_autosave? || custom_validation_context?)
347
+
348
+ begin
349
+ @validating_belongs_to_for ||= {}
350
+ @validating_belongs_to_for[association] = true
351
+ association_valid?(association, record)
352
+ ensure
353
+ @validating_belongs_to_for[association] = false
354
+ end
319
355
  end
320
356
 
321
357
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +360,7 @@ module ActiveRecord
324
360
  def validate_collection_association(reflection)
325
361
  if association = association_instance_get(reflection.name)
326
362
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
327
- records.each_with_index { |record, index| association_valid?(reflection, record, index) }
363
+ records.each { |record| association_valid?(association, record) }
328
364
  end
329
365
  end
330
366
  end
@@ -332,38 +368,33 @@ module ActiveRecord
332
368
  # Returns whether or not the association is valid and applies any errors to
333
369
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
334
370
  # enabled records if they're marked_for_destruction? or destroyed.
335
- def association_valid?(reflection, record, index = nil)
336
- return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
371
+ def association_valid?(association, record)
372
+ return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
337
373
 
338
374
  context = validation_context if custom_validation_context?
375
+ return true if record.valid?(context)
339
376
 
340
- unless valid = record.valid?(context)
341
- if reflection.options[:autosave]
342
- indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord.index_nested_attribute_errors)
377
+ if context || record.changed_for_autosave?
378
+ associated_errors = record.errors.objects
379
+ else
380
+ # If there are existing invalid records in the DB, we should still be able to reference them.
381
+ # Unless a record (no matter where in the association chain) is invalid and is being changed.
382
+ associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
383
+ end
343
384
 
344
- record.errors.group_by_attribute.each { |attribute, errors|
345
- attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
385
+ if association.options[:autosave]
386
+ return if equal?(record)
346
387
 
347
- errors.each { |error|
348
- self.errors.import(
349
- error,
350
- attribute: attribute
351
- )
352
- }
353
- }
354
- else
355
- errors.add(reflection.name)
356
- end
388
+ associated_errors.each { |error|
389
+ errors.objects.append(
390
+ Associations::NestedError.new(association, error)
391
+ )
392
+ }
393
+ elsif associated_errors.any?
394
+ errors.add(association.reflection.name)
357
395
  end
358
- valid
359
- end
360
396
 
361
- def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
362
- if indexed_attribute
363
- "#{reflection.name}[#{index}].#{attribute}"
364
- else
365
- "#{reflection.name}.#{attribute}"
366
- end
397
+ errors.any?
367
398
  end
368
399
 
369
400
  # Is used as an around_save callback to check while saving a collection
@@ -444,33 +475,34 @@ module ActiveRecord
444
475
  return unless association && association.loaded?
445
476
 
446
477
  record = association.load_target
478
+ return unless record && !record.destroyed?
447
479
 
448
- if record && !record.destroyed?
449
- autosave = reflection.options[:autosave]
480
+ autosave = reflection.options[:autosave]
450
481
 
451
- if autosave && record.marked_for_destruction?
452
- record.destroy
453
- elsif autosave != false
454
- primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
455
- primary_key_value = primary_key.map { |key| _read_attribute(key) }
456
-
457
- if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
458
- unless reflection.through_reflection
459
- foreign_key = Array(reflection.foreign_key)
460
- primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
482
+ if autosave && record.marked_for_destruction?
483
+ record.destroy
484
+ elsif autosave != false
485
+ primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
486
+ primary_key_value = primary_key.map { |key| _read_attribute(key) }
487
+ return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
461
488
 
462
- primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
463
- association_id = _read_attribute(primary_key)
464
- record[foreign_key] = association_id unless record[foreign_key] == association_id
465
- end
466
- association.set_inverse_instance(record)
467
- end
489
+ unless reflection.through_reflection
490
+ foreign_key = Array(reflection.foreign_key)
491
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
468
492
 
469
- saved = record.save(validate: !autosave)
470
- raise ActiveRecord::Rollback if !saved && autosave
471
- saved
493
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
494
+ association_id = _read_attribute(primary_key)
495
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
472
496
  end
497
+ association.set_inverse_instance(record)
473
498
  end
499
+
500
+ inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
501
+ return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
502
+
503
+ saved = record.save(validate: !autosave)
504
+ raise ActiveRecord::Rollback if !saved && autosave
505
+ saved
474
506
  end
475
507
  end
476
508
 
@@ -495,8 +527,7 @@ module ActiveRecord
495
527
  return false unless reflection.inverse_of&.polymorphic?
496
528
 
497
529
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
498
-
499
- reflection.active_record != record.class.polymorphic_class_for(class_name)
530
+ reflection.active_record.polymorphic_name != class_name
500
531
  end
501
532
 
502
533
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -515,7 +546,15 @@ module ActiveRecord
515
546
  foreign_key.each { |key| self[key] = nil }
516
547
  record.destroy
517
548
  elsif autosave != false
518
- saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
549
+ saved = if record.new_record? || (autosave && record.changed_for_autosave?)
550
+ begin
551
+ @autosaving_belongs_to_for ||= {}
552
+ @autosaving_belongs_to_for[association] = true
553
+ record.save(validate: !autosave)
554
+ ensure
555
+ @autosaving_belongs_to_for[association] = false
556
+ end
557
+ end
519
558
 
520
559
  if association.updated?
521
560
  primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
@@ -550,10 +589,6 @@ module ActiveRecord
550
589
  end
551
590
  end
552
591
 
553
- def custom_validation_context?
554
- validation_context && [:create, :update].exclude?(validation_context)
555
- end
556
-
557
592
  def _ensure_no_duplicate_errors
558
593
  errors.uniq!
559
594
  end
@@ -6,7 +6,7 @@ require "active_support/descendants_tracker"
6
6
  require "active_support/time"
7
7
  require "active_support/core_ext/class/subclasses"
8
8
  require "active_record/log_subscriber"
9
- require "active_record/explain_subscriber"
9
+ require "active_record/structured_event_subscriber"
10
10
  require "active_record/relation/delegation"
11
11
  require "active_record/attributes"
12
12
  require "active_record/type_caster"
@@ -233,7 +233,7 @@ module ActiveRecord # :nodoc:
233
233
  #
234
234
  # Connections are usually created through
235
235
  # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
236
- # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
236
+ # by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this
237
237
  # connection. But you can also set a class-specific connection. For example, if Course is an
238
238
  # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
239
239
  # and Course and all of its subclasses will use this connection instead.
@@ -256,13 +256,13 @@ module ActiveRecord # :nodoc:
256
256
  # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
257
257
  # specified in the association definition.
258
258
  # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
259
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
259
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
260
260
  # You can inspect the +attribute+ property of the exception object to determine which attribute
261
261
  # triggered the error.
262
262
  # * ConnectionNotEstablished - No connection has been established.
263
263
  # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
264
264
  # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
265
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
265
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
266
266
  # The +errors+ property of this exception contains an array of
267
267
  # AttributeAssignmentError
268
268
  # objects that should be inspected to determine which attributes triggered the errors.
@@ -280,7 +280,7 @@ module ActiveRecord # :nodoc:
280
280
  # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
281
281
  # instances in the current object space.
282
282
  class Base
283
- extend ActiveModel::Naming
283
+ include ActiveModel::API
284
284
 
285
285
  extend ActiveSupport::Benchmarkable
286
286
  extend ActiveSupport::DescendantsTracker
@@ -304,7 +304,6 @@ module ActiveRecord # :nodoc:
304
304
  include Scoping
305
305
  include Sanitization
306
306
  include AttributeAssignment
307
- include ActiveModel::Conversion
308
307
  include Integration
309
308
  include Validations
310
309
  include CounterCache
@@ -329,7 +328,6 @@ module ActiveRecord # :nodoc:
329
328
  include TokenFor
330
329
  include SignedId
331
330
  include Suppressor
332
- include Normalization
333
331
  include Marshalling::Methods
334
332
 
335
333
  self.param_delimiter = "_"
@@ -418,7 +418,7 @@ module ActiveRecord
418
418
 
419
419
  def destroy # :nodoc:
420
420
  @_destroy_callback_already_called ||= false
421
- return if @_destroy_callback_already_called
421
+ return true if @_destroy_callback_already_called
422
422
  @_destroy_callback_already_called = true
423
423
  _run_destroy_callbacks { super }
424
424
  rescue RecordNotDestroyed => e
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/json"
4
+
3
5
  module ActiveRecord
4
6
  module Coders # :nodoc:
5
- module JSON # :nodoc:
6
- def self.dump(obj)
7
- ActiveSupport::JSON.encode(obj)
7
+ class JSON # :nodoc:
8
+ DEFAULT_OPTIONS = { escape: false }.freeze
9
+
10
+ def initialize(options = nil)
11
+ @options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
12
+ @encoder = ActiveSupport::JSON::Encoding.json_encoder.new(options)
13
+ end
14
+
15
+ def dump(obj)
16
+ @encoder.encode(obj)
8
17
  end
9
18
 
10
- def self.load(json)
11
- ActiveSupport::JSON.decode(json) unless json.blank?
19
+ def load(json)
20
+ ActiveSupport::JSON.decode(json, @options) unless json.blank?
12
21
  end
13
22
  end
14
23
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
 
6
5
  module ActiveRecord
@@ -55,31 +54,28 @@ module ActiveRecord
55
54
  # about the model. The model needs to pass a connection specification name to the handler,
56
55
  # in order to look up the correct connection pool.
57
56
  class ConnectionHandler
58
- FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
59
- private_constant :FINALIZER
60
-
61
- class StringConnectionName # :nodoc:
62
- attr_reader :name
63
-
64
- def initialize(name)
57
+ class ConnectionDescriptor # :nodoc:
58
+ def initialize(name, primary = false)
65
59
  @name = name
60
+ @primary = primary
61
+ end
62
+
63
+ def name
64
+ primary_class? ? "ActiveRecord::Base" : @name
66
65
  end
67
66
 
68
67
  def primary_class?
69
- false
68
+ @primary
70
69
  end
71
70
 
72
71
  def current_preventing_writes
73
- false
72
+ ActiveRecord::Base.preventing_writes?(@name)
74
73
  end
75
74
  end
76
75
 
77
76
  def initialize
78
77
  # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
79
78
  @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
80
-
81
- # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
82
- ObjectSpace.define_finalizer self, FINALIZER
83
79
  end
84
80
 
85
81
  def prevent_writes # :nodoc:
@@ -94,22 +90,10 @@ module ActiveRecord
94
90
  connection_name_to_pool_manager.keys
95
91
  end
96
92
 
97
- def all_connection_pools
98
- ActiveRecord.deprecator.warn(<<-MSG.squish)
99
- The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
100
- Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
101
- MSG
102
- connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
103
- end
104
-
105
- # Returns the pools for a connection handler and given role. If +:all+ is passed,
93
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
106
94
  # all pools belonging to the connection handler will be returned.
107
95
  def connection_pool_list(role = nil)
108
- if role.nil?
109
- deprecation_for_pool_handling(__method__)
110
- role = ActiveRecord::Base.current_role
111
- connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
112
- elsif role == :all
96
+ if role.nil? || role == :all
113
97
  connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
114
98
  else
115
99
  connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
@@ -134,7 +118,7 @@ module ActiveRecord
134
118
  pool_config = resolve_pool_config(config, owner_name, role, shard)
135
119
  db_config = pool_config.db_config
136
120
 
137
- pool_manager = set_pool_manager(pool_config.connection_name)
121
+ pool_manager = set_pool_manager(pool_config.connection_descriptor)
138
122
 
139
123
  # If there is an existing pool with the same values as the pool_config
140
124
  # don't remove the connection. Connections should only be removed if we are
@@ -146,8 +130,8 @@ module ActiveRecord
146
130
  # Update the pool_config's connection class if it differs. This is used
147
131
  # for ensuring that ActiveRecord::Base and the primary_abstract_class use
148
132
  # the same pool. Without this granular swapping will not work correctly.
149
- if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
150
- existing_pool_config.connection_class = owner_name
133
+ if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
134
+ existing_pool_config.connection_descriptor = owner_name
151
135
  end
152
136
 
153
137
  existing_pool_config.pool
@@ -156,7 +140,7 @@ module ActiveRecord
156
140
  pool_manager.set_pool_config(role, shard, pool_config)
157
141
 
158
142
  payload = {
159
- connection_name: pool_config.connection_name,
143
+ connection_name: pool_config.connection_descriptor.name,
160
144
  role: role,
161
145
  shard: shard,
162
146
  config: db_config.configuration_hash
@@ -171,44 +155,25 @@ module ActiveRecord
171
155
  # Returns true if there are any active connections among the connection
172
156
  # pools that the ConnectionHandler is managing.
173
157
  def active_connections?(role = nil)
174
- if role.nil?
175
- deprecation_for_pool_handling(__method__)
176
- role = ActiveRecord::Base.current_role
177
- end
178
-
179
158
  each_connection_pool(role).any?(&:active_connection?)
180
159
  end
181
160
 
182
- # Returns any connections in use by the current thread back to the pool,
183
- # and also returns connections to the pool cached by threads that are no
184
- # longer alive.
161
+ # Returns any connections in use by the current thread back to the pool.
185
162
  def clear_active_connections!(role = nil)
186
- if role.nil?
187
- deprecation_for_pool_handling(__method__)
188
- role = ActiveRecord::Base.current_role
163
+ each_connection_pool(role).each do |pool|
164
+ pool.release_connection
165
+ pool.disable_query_cache!
189
166
  end
190
-
191
- each_connection_pool(role).each(&:release_connection)
192
167
  end
193
168
 
194
- # Clears the cache which maps classes.
169
+ # Clears reloadable connection caches in all connection pools.
195
170
  #
196
171
  # See ConnectionPool#clear_reloadable_connections! for details.
197
172
  def clear_reloadable_connections!(role = nil)
198
- if role.nil?
199
- deprecation_for_pool_handling(__method__)
200
- role = ActiveRecord::Base.current_role
201
- end
202
-
203
173
  each_connection_pool(role).each(&:clear_reloadable_connections!)
204
174
  end
205
175
 
206
176
  def clear_all_connections!(role = nil)
207
- if role.nil?
208
- deprecation_for_pool_handling(__method__)
209
- role = ActiveRecord::Base.current_role
210
- end
211
-
212
177
  each_connection_pool(role).each(&:disconnect!)
213
178
  end
214
179
 
@@ -216,11 +181,6 @@ module ActiveRecord
216
181
  #
217
182
  # See ConnectionPool#flush! for details.
218
183
  def flush_idle_connections!(role = nil)
219
- if role.nil?
220
- deprecation_for_pool_handling(__method__)
221
- role = ActiveRecord::Base.current_role
222
- end
223
-
224
184
  each_connection_pool(role).each(&:flush!)
225
185
  end
226
186
 
@@ -229,21 +189,8 @@ module ActiveRecord
229
189
  # opened and set as the active connection for the class it was defined
230
190
  # for (not necessarily the current class).
231
191
  def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
232
- pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
233
-
234
- unless pool
235
- if shard != ActiveRecord::Base.default_shard
236
- message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
237
- elsif role != ActiveRecord::Base.default_role
238
- message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
239
- else
240
- message = "No connection pool for '#{connection_name}' found."
241
- end
242
-
243
- raise ConnectionNotEstablished, message
244
- end
245
-
246
- pool.connection
192
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
193
+ pool.lease_connection
247
194
  end
248
195
 
249
196
  # Returns true if a connection that's accessible to this class has
@@ -262,9 +209,29 @@ module ActiveRecord
262
209
  # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
263
210
  # This makes retrieving the connection pool O(1) once the process is warm.
264
211
  # When a connection is established or removed, we invalidate the cache.
265
- def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
266
- pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
267
- pool_config&.pool
212
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
+ pool_manager = get_pool_manager(connection_name)
214
+ pool = pool_manager&.get_pool_config(role, shard)&.pool
215
+
216
+ if strict && !pool
217
+ selector = [
218
+ ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
219
+ ("'#{role}' role" unless role == ActiveRecord::Base.default_role),
220
+ ].compact.join(" and ")
221
+
222
+ selector = [
223
+ (connection_name unless connection_name == "ActiveRecord::Base"),
224
+ selector.presence,
225
+ ].compact.join(" with ")
226
+
227
+ selector = " for #{selector}" if selector.present?
228
+
229
+ message = "No database connection defined#{selector}."
230
+
231
+ raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
232
+ end
233
+
234
+ pool
268
235
  end
269
236
 
270
237
  private
@@ -276,31 +243,14 @@ module ActiveRecord
276
243
  end
277
244
 
278
245
  # Get the existing pool manager or initialize and assign a new one.
279
- def set_pool_manager(connection_name)
280
- connection_name_to_pool_manager[connection_name] ||= PoolManager.new
246
+ def set_pool_manager(connection_descriptor)
247
+ connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
281
248
  end
282
249
 
283
250
  def pool_managers
284
251
  connection_name_to_pool_manager.values
285
252
  end
286
253
 
287
- def deprecation_for_pool_handling(method)
288
- roles = []
289
- pool_managers.each do |pool_manager|
290
- roles << pool_manager.role_names
291
- end
292
-
293
- if roles.flatten.uniq.count > 1
294
- ActiveRecord.deprecator.warn(<<-MSG.squish)
295
- `#{method}` currently only applies to connection pools in the current
296
- role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
297
- will apply to all known pools, regardless of role. To affect only those
298
- connections belonging to a specific role, pass the role name as an
299
- argument. To switch to the new behavior, pass `:all` as the role name.
300
- MSG
301
- end
302
- end
303
-
304
254
  def disconnect_pool_from_pool_manager(pool_manager, role, shard)
305
255
  pool_config = pool_manager.remove_pool_config(role, shard)
306
256
 
@@ -322,42 +272,16 @@ module ActiveRecord
322
272
  #
323
273
  def resolve_pool_config(config, connection_name, role, shard)
324
274
  db_config = Base.configurations.resolve(config)
325
-
275
+ db_config.validate!
326
276
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
327
-
328
- # Require the adapter itself and give useful feedback about
329
- # 1. Missing adapter gems and
330
- # 2. Adapter gems' missing dependencies.
331
- path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
332
- begin
333
- require path_to_adapter
334
- rescue LoadError => e
335
- # We couldn't require the adapter itself. Raise an exception that
336
- # points out config typos and missing gems.
337
- if e.path == path_to_adapter
338
- # We can assume that a non-builtin adapter was specified, so it's
339
- # either misspelled or missing from Gemfile.
340
- raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
341
-
342
- # Bubbled up from the adapter require. Prefix the exception message
343
- # with some guidance about how to address it and reraise.
344
- else
345
- raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
346
- end
347
- end
348
-
349
- unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
350
- raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
351
- end
352
-
353
277
  ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
354
278
  end
355
279
 
356
280
  def determine_owner_name(owner_name, config)
357
281
  if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
358
- StringConnectionName.new(owner_name.to_s)
282
+ ConnectionDescriptor.new(owner_name.to_s)
359
283
  elsif config.is_a?(Symbol)
360
- StringConnectionName.new(config.to_s)
284
+ ConnectionDescriptor.new(config.to_s)
361
285
  else
362
286
  owner_name
363
287
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "monitor"
5
4
 
6
5
  module ActiveRecord
@@ -41,6 +40,14 @@ module ActiveRecord
41
40
  end
42
41
  end
43
42
 
43
+ # Add +element+ to the back of the queue. Never blocks.
44
+ def add_back(element)
45
+ synchronize do
46
+ @queue.unshift element
47
+ @cond.signal
48
+ end
49
+ end
50
+
44
51
  # If +element+ is in the queue, remove and return it, or +nil+.
45
52
  def delete(element)
46
53
  synchronize do
@@ -55,6 +62,13 @@ module ActiveRecord
55
62
  end
56
63
  end
57
64
 
65
+ # Number of elements in the queue.
66
+ def size
67
+ synchronize do
68
+ @queue.size
69
+ end
70
+ end
71
+
58
72
  # Remove the head of the queue.
59
73
  #
60
74
  # If +timeout+ is not given, remove and return the head of the
@@ -115,9 +129,7 @@ module ActiveRecord
115
129
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
116
130
  elapsed = 0
117
131
  loop do
118
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
119
- @cond.wait(timeout - elapsed)
120
- end
132
+ @cond.wait(timeout - elapsed)
121
133
 
122
134
  return remove if any?
123
135