activerecord 7.2.4 → 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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -1305
  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/join_dependency.rb +2 -2
  13. data/lib/active_record/associations/preloader/association.rb +2 -2
  14. data/lib/active_record/associations/singular_association.rb +8 -3
  15. data/lib/active_record/associations.rb +50 -32
  16. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  17. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  18. data/lib/active_record/attribute_methods.rb +18 -25
  19. data/lib/active_record/attributes.rb +26 -37
  20. data/lib/active_record/autosave_association.rb +81 -49
  21. data/lib/active_record/base.rb +2 -2
  22. data/lib/active_record/callbacks.rb +1 -1
  23. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
  27. data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
  28. data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
  29. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  30. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
  31. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
  32. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +51 -86
  34. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
  35. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
  36. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  37. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  38. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  39. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -16
  40. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  41. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  43. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
  44. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  45. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
  46. data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
  47. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  48. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  49. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
  50. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  51. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  52. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
  53. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
  54. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  55. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  56. data/lib/active_record/connection_handling.rb +29 -11
  57. data/lib/active_record/core.rb +15 -60
  58. data/lib/active_record/counter_cache.rb +1 -1
  59. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
  60. data/lib/active_record/delegated_type.rb +18 -18
  61. data/lib/active_record/encryption/config.rb +3 -1
  62. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  63. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  64. data/lib/active_record/encryption/encryptor.rb +35 -29
  65. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  66. data/lib/active_record/encryption/scheme.rb +8 -1
  67. data/lib/active_record/enum.rb +12 -13
  68. data/lib/active_record/errors.rb +16 -8
  69. data/lib/active_record/fixture_set/table_row.rb +2 -19
  70. data/lib/active_record/fixtures.rb +0 -1
  71. data/lib/active_record/future_result.rb +14 -10
  72. data/lib/active_record/gem_version.rb +4 -4
  73. data/lib/active_record/insert_all.rb +1 -1
  74. data/lib/active_record/marshalling.rb +1 -4
  75. data/lib/active_record/migration/command_recorder.rb +22 -5
  76. data/lib/active_record/migration/compatibility.rb +5 -2
  77. data/lib/active_record/migration.rb +36 -35
  78. data/lib/active_record/model_schema.rb +1 -1
  79. data/lib/active_record/nested_attributes.rb +4 -6
  80. data/lib/active_record/persistence.rb +128 -130
  81. data/lib/active_record/query_cache.rb +5 -4
  82. data/lib/active_record/query_logs.rb +98 -44
  83. data/lib/active_record/query_logs_formatter.rb +17 -28
  84. data/lib/active_record/querying.rb +10 -10
  85. data/lib/active_record/railtie.rb +5 -6
  86. data/lib/active_record/railties/databases.rake +1 -2
  87. data/lib/active_record/reflection.rb +9 -7
  88. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  89. data/lib/active_record/relation/batches.rb +132 -72
  90. data/lib/active_record/relation/calculations.rb +55 -55
  91. data/lib/active_record/relation/delegation.rb +25 -14
  92. data/lib/active_record/relation/finder_methods.rb +31 -32
  93. data/lib/active_record/relation/merger.rb +8 -8
  94. data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
  95. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  96. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  97. data/lib/active_record/relation/predicate_builder.rb +5 -0
  98. data/lib/active_record/relation/query_attribute.rb +1 -1
  99. data/lib/active_record/relation/query_methods.rb +90 -91
  100. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  101. data/lib/active_record/relation/spawn_methods.rb +1 -1
  102. data/lib/active_record/relation/where_clause.rb +2 -8
  103. data/lib/active_record/relation.rb +77 -76
  104. data/lib/active_record/result.rb +68 -7
  105. data/lib/active_record/sanitization.rb +7 -6
  106. data/lib/active_record/schema_dumper.rb +16 -29
  107. data/lib/active_record/schema_migration.rb +2 -1
  108. data/lib/active_record/scoping/named.rb +5 -2
  109. data/lib/active_record/secure_token.rb +3 -3
  110. data/lib/active_record/signed_id.rb +6 -7
  111. data/lib/active_record/statement_cache.rb +12 -12
  112. data/lib/active_record/store.rb +7 -3
  113. data/lib/active_record/tasks/database_tasks.rb +24 -15
  114. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  115. data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
  116. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  117. data/lib/active_record/test_fixtures.rb +12 -0
  118. data/lib/active_record/testing/query_assertions.rb +2 -2
  119. data/lib/active_record/token_for.rb +1 -1
  120. data/lib/active_record/transactions.rb +1 -3
  121. data/lib/active_record/validations/uniqueness.rb +8 -8
  122. data/lib/active_record.rb +16 -1
  123. data/lib/arel/collectors/bind.rb +1 -1
  124. data/lib/arel/crud.rb +0 -2
  125. data/lib/arel/delete_manager.rb +0 -5
  126. data/lib/arel/nodes/delete_statement.rb +2 -4
  127. data/lib/arel/nodes/update_statement.rb +2 -4
  128. data/lib/arel/select_manager.rb +2 -6
  129. data/lib/arel/update_manager.rb +0 -5
  130. data/lib/arel/visitors/dot.rb +0 -2
  131. data/lib/arel/visitors/sqlite.rb +0 -25
  132. data/lib/arel/visitors/to_sql.rb +1 -3
  133. metadata +14 -11
@@ -1,29 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+ require "concurrent/atomic/read_write_lock"
5
+
3
6
  module ActiveRecord
4
7
  class AsynchronousQueriesTracker # :nodoc:
5
- module NullSession # :nodoc:
6
- class << self
7
- def active?
8
- true
9
- end
10
-
11
- def finalize
12
- end
13
- end
14
- end
15
-
16
8
  class Session # :nodoc:
17
9
  def initialize
18
- @active = true
10
+ @active = Concurrent::AtomicBoolean.new(true)
11
+ @lock = Concurrent::ReadWriteLock.new
19
12
  end
20
13
 
21
14
  def active?
22
- @active
15
+ @active.true?
23
16
  end
24
17
 
25
- def finalize
26
- @active = false
18
+ def synchronize(&block)
19
+ @lock.with_read_lock(&block)
20
+ end
21
+
22
+ def finalize(wait = false)
23
+ @active.make_false
24
+ if wait
25
+ # Wait until all thread with a read lock are done
26
+ @lock.with_write_lock { }
27
+ end
27
28
  end
28
29
  end
29
30
 
@@ -33,7 +34,7 @@ module ActiveRecord
33
34
  end
34
35
 
35
36
  def run
36
- ActiveRecord::Base.asynchronous_queries_tracker.start_session
37
+ ActiveRecord::Base.asynchronous_queries_tracker.tap(&:start_session)
37
38
  end
38
39
 
39
40
  def complete(asynchronous_queries_tracker)
@@ -41,20 +42,23 @@ module ActiveRecord
41
42
  end
42
43
  end
43
44
 
44
- attr_reader :current_session
45
-
46
45
  def initialize
47
- @current_session = NullSession
46
+ @stack = []
47
+ end
48
+
49
+ def current_session
50
+ @stack.last or raise ActiveRecordError, "Can't perform asynchronous queries without a query session"
48
51
  end
49
52
 
50
53
  def start_session
51
- @current_session = Session.new
52
- self
54
+ session = Session.new
55
+ @stack << session
53
56
  end
54
57
 
55
- def finalize_session
56
- @current_session.finalize
57
- @current_session = NullSession
58
+ def finalize_session(wait = false)
59
+ session = @stack.pop
60
+ session&.finalize(wait)
61
+ self
58
62
  end
59
63
  end
60
64
  end
@@ -130,7 +130,7 @@ module ActiveRecord
130
130
  # silently cast unsupported types to +String+:
131
131
  #
132
132
  # >> JSON.parse(JSON.dump(Struct.new(:foo)))
133
- # # => "#<Class:0x000000013090b4c0>"
133
+ # => "#<Class:0x000000013090b4c0>"
134
134
  #
135
135
  # ==== Examples
136
136
  #
@@ -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,29 +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 = if self.class.public_method_defined?(name)
484
- begin
485
- self.class.public_instance_method(name)
486
- rescue NameError
487
- 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
488
  end
489
489
  end
490
490
 
491
- # The method might be explicitly defined in the model, but call a generated
492
- # method with super. So we must resume the call chain at the right step.
493
- method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
494
- if method
495
- method.bind_call(self, ...)
496
- else
497
- super
498
- end
491
+ super
499
492
  end
500
493
 
501
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