activerecord 7.2.3 → 8.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -1261
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/associations/alias_tracker.rb +4 -6
  5. data/lib/active_record/associations/association.rb +25 -5
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -18
  7. data/lib/active_record/associations/builder/association.rb +7 -6
  8. data/lib/active_record/associations/collection_association.rb +4 -4
  9. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  10. data/lib/active_record/associations/has_many_through_association.rb +4 -9
  11. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  12. data/lib/active_record/associations/preloader/association.rb +2 -2
  13. data/lib/active_record/associations/singular_association.rb +8 -3
  14. data/lib/active_record/associations.rb +50 -32
  15. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  16. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +19 -24
  18. data/lib/active_record/attributes.rb +26 -37
  19. data/lib/active_record/autosave_association.rb +81 -49
  20. data/lib/active_record/base.rb +2 -2
  21. data/lib/active_record/callbacks.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  27. data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
  28. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -57
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
  35. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  36. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  37. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  38. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -16
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  48. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
  49. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  50. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  55. data/lib/active_record/connection_handling.rb +29 -11
  56. data/lib/active_record/core.rb +15 -60
  57. data/lib/active_record/counter_cache.rb +1 -1
  58. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
  59. data/lib/active_record/delegated_type.rb +18 -18
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  63. data/lib/active_record/encryption/encryptor.rb +35 -29
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +12 -13
  67. data/lib/active_record/errors.rb +16 -8
  68. data/lib/active_record/fixture_set/table_row.rb +2 -19
  69. data/lib/active_record/fixtures.rb +0 -1
  70. data/lib/active_record/future_result.rb +14 -10
  71. data/lib/active_record/gem_version.rb +4 -4
  72. data/lib/active_record/insert_all.rb +1 -1
  73. data/lib/active_record/marshalling.rb +1 -4
  74. data/lib/active_record/migration/command_recorder.rb +22 -5
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +36 -35
  77. data/lib/active_record/model_schema.rb +1 -1
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_cache.rb +5 -4
  81. data/lib/active_record/query_logs.rb +98 -44
  82. data/lib/active_record/query_logs_formatter.rb +17 -28
  83. data/lib/active_record/querying.rb +10 -10
  84. data/lib/active_record/railtie.rb +5 -6
  85. data/lib/active_record/railties/databases.rake +1 -2
  86. data/lib/active_record/reflection.rb +9 -7
  87. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  88. data/lib/active_record/relation/batches.rb +132 -72
  89. data/lib/active_record/relation/calculations.rb +55 -55
  90. data/lib/active_record/relation/delegation.rb +25 -14
  91. data/lib/active_record/relation/finder_methods.rb +31 -32
  92. data/lib/active_record/relation/merger.rb +8 -8
  93. data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
  94. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  95. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  96. data/lib/active_record/relation/predicate_builder.rb +5 -0
  97. data/lib/active_record/relation/query_attribute.rb +1 -1
  98. data/lib/active_record/relation/query_methods.rb +90 -91
  99. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  100. data/lib/active_record/relation/spawn_methods.rb +1 -1
  101. data/lib/active_record/relation/where_clause.rb +2 -8
  102. data/lib/active_record/relation.rb +77 -76
  103. data/lib/active_record/result.rb +68 -7
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +16 -29
  106. data/lib/active_record/schema_migration.rb +2 -1
  107. data/lib/active_record/scoping/named.rb +5 -2
  108. data/lib/active_record/secure_token.rb +3 -3
  109. data/lib/active_record/signed_id.rb +6 -7
  110. data/lib/active_record/statement_cache.rb +12 -12
  111. data/lib/active_record/store.rb +7 -3
  112. data/lib/active_record/tasks/database_tasks.rb +24 -15
  113. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  114. data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
  115. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  116. data/lib/active_record/test_fixtures.rb +12 -0
  117. data/lib/active_record/testing/query_assertions.rb +2 -2
  118. data/lib/active_record/token_for.rb +1 -1
  119. data/lib/active_record/transactions.rb +1 -3
  120. data/lib/active_record/validations/uniqueness.rb +8 -8
  121. data/lib/active_record.rb +16 -1
  122. data/lib/arel/collectors/bind.rb +1 -1
  123. data/lib/arel/crud.rb +0 -2
  124. data/lib/arel/delete_manager.rb +0 -5
  125. data/lib/arel/nodes/delete_statement.rb +2 -4
  126. data/lib/arel/nodes/update_statement.rb +2 -4
  127. data/lib/arel/select_manager.rb +2 -6
  128. data/lib/arel/update_manager.rb +0 -5
  129. data/lib/arel/visitors/dot.rb +0 -2
  130. data/lib/arel/visitors/sqlite.rb +0 -25
  131. data/lib/arel/visitors/to_sql.rb +1 -3
  132. metadata +14 -11
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  attribute_method_patterns_cache.clear
85
85
  end
86
86
 
87
- def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
88
88
  old_name = old_name.to_s
89
89
 
90
90
  if !abstract_class? && !has_attribute?(old_name)
@@ -113,14 +113,13 @@ module ActiveRecord
113
113
  unless abstract_class?
114
114
  load_schema
115
115
  super(attribute_names)
116
- alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
116
+ alias_attribute :id_value, :id if _has_attribute?("id")
117
117
  end
118
118
 
119
- generate_alias_attributes
120
-
121
119
  @attribute_methods_generated = true
122
- end
123
120
 
121
+ generate_alias_attributes
122
+ end
124
123
  true
125
124
  end
126
125
 
@@ -473,27 +472,23 @@ module ActiveRecord
473
472
  end
474
473
 
475
474
  def method_missing(name, ...)
476
- # We can't know whether some method was defined or not because
477
- # multiple thread might be concurrently be in this code path.
478
- # So the first one would define the methods and the others would
479
- # appear to already have them.
480
- self.class.define_attribute_methods
481
-
482
- # So in all cases we must behave as if the method was just defined.
483
- method = begin
484
- self.class.public_instance_method(name)
485
- rescue NameError
486
- nil
475
+ unless self.class.attribute_methods_generated?
476
+ if self.class.method_defined?(name)
477
+ # The method is explicitly defined in the model, but calls a generated
478
+ # method with super. So we must resume the call chain at the right step.
479
+ last_method = method(name)
480
+ last_method = last_method.super_method while last_method.super_method
481
+ self.class.define_attribute_methods
482
+ if last_method.super_method
483
+ return last_method.super_method.call(...)
484
+ end
485
+ elsif self.class.define_attribute_methods
486
+ # Some attribute methods weren't generated yet, we retry the call
487
+ return public_send(name, ...)
488
+ end
487
489
  end
488
490
 
489
- # The method might be explicitly defined in the model, but call a generated
490
- # method with super. So we must resume the call chain at the right step.
491
- method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
492
- if method
493
- method.bind_call(self, ...)
494
- else
495
- super
496
- end
491
+ super
497
492
  end
498
493
 
499
494
  def attribute_method?(attr_name)
@@ -21,34 +21,28 @@ module ActiveRecord
21
21
  # your domain objects across much of Active Record, without having to
22
22
  # rely on implementation details or monkey patching.
23
23
  #
24
- # ==== Parameters
24
+ # +name+ The name of the methods to define attribute methods for, and the
25
+ # column which this will persist to.
25
26
  #
26
- # [+name+]
27
- # The name of the methods to define attribute methods for, and the
28
- # column which this will persist to.
29
- #
30
- # [+cast_type+]
31
- # A symbol such as +:string+ or +:integer+, or a type object to be used
32
- # for this attribute. If this parameter is not passed, the previously
33
- # defined type (if any) will be used. Otherwise, the type will be
34
- # ActiveModel::Type::Value. See the examples below for more information
35
- # about providing custom type objects.
27
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
28
+ # to be used for this attribute. If this parameter is not passed, the previously
29
+ # defined type (if any) will be used.
30
+ # Otherwise, the type will be ActiveModel::Type::Value.
31
+ # See the examples below for more information about providing custom type objects.
36
32
  #
37
33
  # ==== Options
38
34
  #
39
- # [+:default+]
40
- # The default value to use when no value is provided. If this option is
41
- # not passed, the previously defined default value (if any) on the
42
- # superclass or in the schema will be used. Otherwise, the default will
43
- # be +nil+.
35
+ # The following options are accepted:
36
+ #
37
+ # +default+ The default value to use when no value is provided. If this option
38
+ # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
39
+ # Otherwise, the default will be +nil+.
44
40
  #
45
- # [+:array+]
46
- # (PostgreSQL only) Specifies that the type should be an array. See the
47
- # examples below.
41
+ # +array+ (PostgreSQL only) specifies that the type should be an array (see the
42
+ # examples below).
48
43
  #
49
- # [+:range+]
50
- # (PostgreSQL only) Specifies that the type should be a range. See the
51
- # examples below.
44
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
45
+ # examples below).
52
46
  #
53
47
  # When using a symbol for +cast_type+, extra options are forwarded to the
54
48
  # constructor of the type object.
@@ -184,8 +178,8 @@ module ActiveRecord
184
178
  # @currency_converter = currency_converter
185
179
  # end
186
180
  #
187
- # # value will be the result of #deserialize or
188
- # # #cast. Assumed to be an instance of Money in
181
+ # # value will be the result of +deserialize+ or
182
+ # # +cast+. Assumed to be an instance of +Money+ in
189
183
  # # this case.
190
184
  # def serialize(value)
191
185
  # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
@@ -223,22 +217,17 @@ module ActiveRecord
223
217
  # is provided so it can be used by plugin authors, application code
224
218
  # should probably use ClassMethods#attribute.
225
219
  #
226
- # ==== Parameters
227
- #
228
- # [+name+]
229
- # The name of the attribute being defined. Expected to be a +String+.
220
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
230
221
  #
231
- # [+cast_type+]
232
- # The type object to use for this attribute.
222
+ # +cast_type+ The type object to use for this attribute.
233
223
  #
234
- # [+default+]
235
- # The default value to use when no value is provided. If this option
236
- # is not passed, the previous default value (if any) will be used.
237
- # Otherwise, the default will be +nil+. A proc can also be passed, and
238
- # will be called once each time a new value is needed.
224
+ # +default+ The default value to use when no value is provided. If this option
225
+ # is not passed, the previous default value (if any) will be used.
226
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
227
+ # will be called once each time a new value is needed.
239
228
  #
240
- # [+user_provided_default+]
241
- # Whether the default value should be cast using +cast+ or +deserialize+.
229
+ # +user_provided_default+ Whether the default value should be cast using
230
+ # +cast+ or +deserialize+.
242
231
  def define_attribute(
243
232
  name,
244
233
  cast_type,
@@ -221,8 +221,10 @@ module ActiveRecord
221
221
  if reflection.validate? && !method_defined?(validation_method)
222
222
  if reflection.collection?
223
223
  method = :validate_collection_association
224
+ elsif reflection.has_one?
225
+ method = :validate_has_one_association
224
226
  else
225
- method = :validate_single_association
227
+ method = :validate_belongs_to_association
226
228
  end
227
229
 
228
230
  define_non_cyclic_method(validation_method) { send(method, reflection) }
@@ -274,6 +276,16 @@ module ActiveRecord
274
276
  new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
275
277
  end
276
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
+
277
289
  private
278
290
  def init_internals
279
291
  super
@@ -313,11 +325,33 @@ module ActiveRecord
313
325
  end
314
326
 
315
327
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
316
- # turned on for the association.
317
- def validate_single_association(reflection)
328
+ # turned on for the has_one association.
329
+ def validate_has_one_association(reflection)
330
+ association = association_instance_get(reflection.name)
331
+ record = association && association.reader
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)
318
344
  association = association_instance_get(reflection.name)
319
345
  record = association && association.reader
320
- association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
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
321
355
  end
322
356
 
323
357
  # Validate the associated records if <tt>:validate</tt> or
@@ -338,29 +372,19 @@ module ActiveRecord
338
372
  return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
339
373
 
340
374
  context = validation_context if custom_validation_context?
341
- return true if record.valid?(context)
342
375
 
343
- if context || record.changed_for_autosave?
344
- associated_errors = record.errors.objects
345
- else
346
- # If there are existing invalid records in the DB, we should still be able to reference them.
347
- # Unless a record (no matter where in the association chain) is invalid and is being changed.
348
- associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
349
- end
350
-
351
- if association.options[:autosave]
352
- return if equal?(record)
353
-
354
- associated_errors.each { |error|
355
- errors.objects.append(
356
- Associations::NestedError.new(association, error)
357
- )
358
- }
359
- elsif associated_errors.any?
360
- errors.add(association.reflection.name)
376
+ unless valid = record.valid?(context)
377
+ if association.options[:autosave]
378
+ record.errors.each { |error|
379
+ self.errors.objects.append(
380
+ Associations::NestedError.new(association, error)
381
+ )
382
+ }
383
+ else
384
+ errors.add(association.reflection.name)
385
+ end
361
386
  end
362
-
363
- errors.any?
387
+ valid
364
388
  end
365
389
 
366
390
  # Is used as an around_save callback to check while saving a collection
@@ -441,33 +465,34 @@ module ActiveRecord
441
465
  return unless association && association.loaded?
442
466
 
443
467
  record = association.load_target
468
+ return unless record && !record.destroyed?
444
469
 
445
- if record && !record.destroyed?
446
- autosave = reflection.options[:autosave]
447
-
448
- if autosave && record.marked_for_destruction?
449
- record.destroy
450
- elsif autosave != false
451
- primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
452
- primary_key_value = primary_key.map { |key| _read_attribute(key) }
470
+ autosave = reflection.options[:autosave]
453
471
 
454
- if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
455
- unless reflection.through_reflection
456
- foreign_key = Array(reflection.foreign_key)
457
- primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
472
+ if autosave && record.marked_for_destruction?
473
+ record.destroy
474
+ elsif autosave != false
475
+ primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
476
+ primary_key_value = primary_key.map { |key| _read_attribute(key) }
477
+ return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
458
478
 
459
- primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
460
- association_id = _read_attribute(primary_key)
461
- record[foreign_key] = association_id unless record[foreign_key] == association_id
462
- end
463
- association.set_inverse_instance(record)
464
- end
479
+ unless reflection.through_reflection
480
+ foreign_key = Array(reflection.foreign_key)
481
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
465
482
 
466
- saved = record.save(validate: !autosave)
467
- raise ActiveRecord::Rollback if !saved && autosave
468
- saved
483
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
484
+ association_id = _read_attribute(primary_key)
485
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
469
486
  end
487
+ association.set_inverse_instance(record)
470
488
  end
489
+
490
+ inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
491
+ return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
492
+
493
+ saved = record.save(validate: !autosave)
494
+ raise ActiveRecord::Rollback if !saved && autosave
495
+ saved
471
496
  end
472
497
  end
473
498
 
@@ -492,8 +517,7 @@ module ActiveRecord
492
517
  return false unless reflection.inverse_of&.polymorphic?
493
518
 
494
519
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
495
-
496
- reflection.active_record.polymorphic_name != class_name
520
+ reflection.active_record != record.class.polymorphic_class_for(class_name)
497
521
  end
498
522
 
499
523
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -512,7 +536,15 @@ module ActiveRecord
512
536
  foreign_key.each { |key| self[key] = nil }
513
537
  record.destroy
514
538
  elsif autosave != false
515
- saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
539
+ saved = if record.new_record? || (autosave && record.changed_for_autosave?)
540
+ begin
541
+ @autosaving_belongs_to_for ||= {}
542
+ @autosaving_belongs_to_for[association] = true
543
+ record.save(validate: !autosave)
544
+ ensure
545
+ @autosaving_belongs_to_for[association] = false
546
+ end
547
+ end
516
548
 
517
549
  if association.updated?
518
550
  primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
@@ -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:ActiveModel::AttributeAssignment#attributes=] method.
259
+ # {ActiveRecord::Base#attributes=}[rdoc-ref: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:ActiveModel::AttributeAssignment#attributes=] method.
265
+ # {ActiveRecord::Base#attributes=}[rdoc-ref: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.
@@ -418,7 +418,7 @@ module ActiveRecord
418
418
 
419
419
  def destroy # :nodoc:
420
420
  @_destroy_callback_already_called ||= false
421
- return true if @_destroy_callback_already_called
421
+ return if @_destroy_callback_already_called
422
422
  @_destroy_callback_already_called = true
423
423
  _run_destroy_callbacks { super }
424
424
  rescue RecordNotDestroyed => e
@@ -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
@@ -210,18 +209,25 @@ module ActiveRecord
210
209
  # This makes retrieving the connection pool O(1) once the process is warm.
211
210
  # When a connection is established or removed, we invalidate the cache.
212
211
  def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
- pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
212
+ pool_manager = get_pool_manager(connection_name)
213
+ pool = pool_manager&.get_pool_config(role, shard)&.pool
214
214
 
215
215
  if strict && !pool
216
- if shard != ActiveRecord::Base.default_shard
217
- message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
218
- elsif role != ActiveRecord::Base.default_role
219
- message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
220
- else
221
- message = "No connection pool for '#{connection_name}' found."
222
- end
216
+ selector = [
217
+ ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
218
+ ("'#{role}' role" unless role == ActiveRecord::Base.default_role),
219
+ ].compact.join(" and ")
220
+
221
+ selector = [
222
+ (connection_name unless connection_name == "ActiveRecord::Base"),
223
+ selector.presence,
224
+ ].compact.join(" with ")
225
+
226
+ selector = " for #{selector}" if selector.present?
227
+
228
+ message = "No database connection defined#{selector}."
223
229
 
224
- raise ConnectionNotEstablished, message
230
+ raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
225
231
  end
226
232
 
227
233
  pool
@@ -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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "weakref"
5
4
 
6
5
  module ActiveRecord
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
  require "monitor"
6
5
 
@@ -38,7 +37,6 @@ module ActiveRecord
38
37
 
39
38
  def schema_cache; end
40
39
  def connection_class; end
41
- def query_cache; end
42
40
  def checkin(_); end
43
41
  def remove(_); end
44
42
  def async_executor; end
@@ -119,30 +117,24 @@ module ActiveRecord
119
117
  # * private methods that require being called in a +synchronize+ blocks
120
118
  # are now explicitly documented
121
119
  class ConnectionPool
122
- # Prior to 3.3.5, WeakKeyMap had a use after free bug
123
- # https://bugs.ruby-lang.org/issues/20688
124
- if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
125
- WeakThreadKeyMap = ObjectSpace::WeakKeyMap
126
- else
127
- class WeakThreadKeyMap # :nodoc:
128
- # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
129
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
130
- def initialize
131
- @map = {}
132
- end
120
+ class WeakThreadKeyMap # :nodoc:
121
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
122
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
123
+ def initialize
124
+ @map = {}
125
+ end
133
126
 
134
- def clear
135
- @map.clear
136
- end
127
+ def clear
128
+ @map.clear
129
+ end
137
130
 
138
- def [](key)
139
- @map[key]
140
- end
131
+ def [](key)
132
+ @map[key]
133
+ end
141
134
 
142
- def []=(key, value)
143
- @map.select! { |c, _| c&.alive? }
144
- @map[key] = value
145
- end
135
+ def []=(key, value)
136
+ @map.select! { |c, _| c.alive? }
137
+ @map[key] = value
146
138
  end
147
139
  end
148
140
 
@@ -191,31 +183,6 @@ module ActiveRecord
191
183
  end
192
184
  end
193
185
 
194
- module ExecutorHooks # :nodoc:
195
- class << self
196
- def run
197
- # noop
198
- end
199
-
200
- def complete(_)
201
- ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
202
- if (connection = pool.active_connection?)
203
- transaction = connection.current_transaction
204
- if transaction.closed? || !transaction.joinable?
205
- pool.release_connection
206
- end
207
- end
208
- end
209
- end
210
- end
211
- end
212
-
213
- class << self
214
- def install_executor_hooks(executor = ActiveSupport::Executor)
215
- executor.register_hook(ExecutorHooks)
216
- end
217
- end
218
-
219
186
  include MonitorMixin
220
187
  prepend QueryCache::ConnectionPoolConfiguration
221
188
  include ConnectionAdapters::AbstractPool
@@ -316,9 +283,8 @@ module ActiveRecord
316
283
  # held in a cache keyed by a thread.
317
284
  def lease_connection
318
285
  lease = connection_lease
319
- lease.connection ||= checkout
320
286
  lease.sticky = true
321
- lease.connection
287
+ lease.connection ||= checkout
322
288
  end
323
289
 
324
290
  def permanent_lease? # :nodoc:
@@ -344,7 +310,6 @@ module ActiveRecord
344
310
  end
345
311
 
346
312
  @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
347
- @pinned_connection.pinned = true
348
313
  @pinned_connection.verify! # eagerly validate the connection
349
314
  @pinned_connection.begin_transaction joinable: false, _lazy: false
350
315
  end
@@ -367,8 +332,6 @@ module ActiveRecord
367
332
  end
368
333
 
369
334
  if @pinned_connection.nil?
370
- connection.pinned = false
371
- connection.steal!
372
335
  connection.lock_thread = nil
373
336
  checkin(connection)
374
337
  end
@@ -558,25 +521,20 @@ module ActiveRecord
558
521
  # Raises:
559
522
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
560
523
  def checkout(checkout_timeout = @checkout_timeout)
561
- return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
562
-
563
- @pinned_connection.lock.synchronize do
564
- synchronize do
565
- # The pinned connection may have been cleaned up before we synchronized, so check if it is still present
566
- if @pinned_connection
524
+ if @pinned_connection
525
+ @pinned_connection.lock.synchronize do
526
+ synchronize do
567
527
  @pinned_connection.verify!
568
-
569
528
  # Any leased connection must be in @connections otherwise
570
529
  # some methods like #connected? won't behave correctly
571
530
  unless @connections.include?(@pinned_connection)
572
531
  @connections << @pinned_connection
573
532
  end
574
-
575
- @pinned_connection
576
- else
577
- checkout_and_verify(acquire_connection(checkout_timeout))
578
533
  end
579
534
  end
535
+ @pinned_connection
536
+ else
537
+ checkout_and_verify(acquire_connection(checkout_timeout))
580
538
  end
581
539
  end
582
540
 
@@ -711,14 +669,6 @@ module ActiveRecord
711
669
  Thread.pass
712
670
  end
713
671
 
714
- def new_connection # :nodoc:
715
- connection = db_config.new_connection
716
- connection.pool = self
717
- connection
718
- rescue ConnectionNotEstablished => ex
719
- raise ex.set_pool(self)
720
- end
721
-
722
672
  private
723
673
  def connection_lease
724
674
  @leases[ActiveSupport::IsolatedExecutionState.context]
@@ -898,12 +848,18 @@ module ActiveRecord
898
848
  #--
899
849
  # if owner_thread param is omitted, this must be called in synchronize block
900
850
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
901
- if owner_thread
902
- @leases[owner_thread].clear(conn)
903
- end
851
+ @leases[owner_thread].clear(conn)
904
852
  end
905
853
  alias_method :release, :remove_connection_from_thread_cache
906
854
 
855
+ def new_connection
856
+ connection = db_config.new_connection
857
+ connection.pool = self
858
+ connection
859
+ rescue ConnectionNotEstablished => ex
860
+ raise ex.set_pool(self)
861
+ end
862
+
907
863
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
908
864
  # to the DB is done outside main synchronized section.
909
865
  #--