activerecord 7.2.2.1 → 8.0.5

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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -677
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +34 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +7 -6
  9. data/lib/active_record/associations/collection_association.rb +10 -8
  10. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  12. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  13. data/lib/active_record/associations/join_dependency.rb +2 -2
  14. data/lib/active_record/associations/preloader/association.rb +2 -2
  15. data/lib/active_record/associations/preloader/batch.rb +7 -1
  16. data/lib/active_record/associations/singular_association.rb +8 -3
  17. data/lib/active_record/associations.rb +34 -4
  18. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  19. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  20. data/lib/active_record/attribute_methods/query.rb +34 -0
  21. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  22. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  23. data/lib/active_record/attribute_methods.rb +24 -19
  24. data/lib/active_record/attributes.rb +37 -26
  25. data/lib/active_record/autosave_association.rb +91 -39
  26. data/lib/active_record/base.rb +2 -2
  27. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  28. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  29. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  30. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
  37. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
  40. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
  45. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  50. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
  56. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  60. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  61. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  62. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
  63. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
  64. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  65. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  66. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  67. data/lib/active_record/connection_adapters.rb +0 -56
  68. data/lib/active_record/connection_handling.rb +35 -9
  69. data/lib/active_record/core.rb +57 -22
  70. data/lib/active_record/counter_cache.rb +1 -1
  71. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  72. data/lib/active_record/database_configurations/database_config.rb +4 -0
  73. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  74. data/lib/active_record/delegated_type.rb +18 -18
  75. data/lib/active_record/encryption/config.rb +3 -1
  76. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  77. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  78. data/lib/active_record/encryption/encryptor.rb +37 -28
  79. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  80. data/lib/active_record/encryption/scheme.rb +8 -1
  81. data/lib/active_record/enum.rb +22 -34
  82. data/lib/active_record/errors.rb +16 -8
  83. data/lib/active_record/fixture_set/table_row.rb +19 -2
  84. data/lib/active_record/fixtures.rb +0 -2
  85. data/lib/active_record/future_result.rb +15 -9
  86. data/lib/active_record/gem_version.rb +4 -4
  87. data/lib/active_record/insert_all.rb +3 -3
  88. data/lib/active_record/locking/optimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +5 -11
  90. data/lib/active_record/migration/command_recorder.rb +32 -12
  91. data/lib/active_record/migration/compatibility.rb +5 -2
  92. data/lib/active_record/migration.rb +40 -43
  93. data/lib/active_record/model_schema.rb +3 -4
  94. data/lib/active_record/nested_attributes.rb +4 -6
  95. data/lib/active_record/persistence.rb +128 -130
  96. data/lib/active_record/query_logs.rb +106 -50
  97. data/lib/active_record/query_logs_formatter.rb +17 -28
  98. data/lib/active_record/querying.rb +12 -12
  99. data/lib/active_record/railtie.rb +4 -28
  100. data/lib/active_record/railties/databases.rake +11 -34
  101. data/lib/active_record/reflection.rb +18 -21
  102. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  103. data/lib/active_record/relation/batches.rb +132 -72
  104. data/lib/active_record/relation/calculations.rb +71 -65
  105. data/lib/active_record/relation/delegation.rb +25 -14
  106. data/lib/active_record/relation/finder_methods.rb +32 -31
  107. data/lib/active_record/relation/merger.rb +8 -8
  108. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  109. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  110. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  111. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  112. data/lib/active_record/relation/predicate_builder.rb +13 -0
  113. data/lib/active_record/relation/query_attribute.rb +1 -1
  114. data/lib/active_record/relation/query_methods.rb +121 -70
  115. data/lib/active_record/relation/spawn_methods.rb +7 -7
  116. data/lib/active_record/relation/where_clause.rb +9 -3
  117. data/lib/active_record/relation.rb +95 -67
  118. data/lib/active_record/result.rb +66 -4
  119. data/lib/active_record/sanitization.rb +7 -6
  120. data/lib/active_record/schema_dumper.rb +34 -11
  121. data/lib/active_record/schema_migration.rb +2 -1
  122. data/lib/active_record/scoping/named.rb +5 -2
  123. data/lib/active_record/secure_token.rb +3 -3
  124. data/lib/active_record/signed_id.rb +7 -6
  125. data/lib/active_record/statement_cache.rb +14 -14
  126. data/lib/active_record/store.rb +7 -3
  127. data/lib/active_record/table_metadata.rb +1 -3
  128. data/lib/active_record/tasks/database_tasks.rb +69 -60
  129. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  130. data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
  131. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  132. data/lib/active_record/test_databases.rb +1 -1
  133. data/lib/active_record/test_fixtures.rb +12 -0
  134. data/lib/active_record/token_for.rb +1 -1
  135. data/lib/active_record/transactions.rb +8 -7
  136. data/lib/active_record/type/serialized.rb +5 -0
  137. data/lib/active_record/validations/uniqueness.rb +8 -8
  138. data/lib/active_record.rb +22 -49
  139. data/lib/arel/collectors/bind.rb +2 -2
  140. data/lib/arel/collectors/sql_string.rb +1 -1
  141. data/lib/arel/collectors/substitute_binds.rb +2 -2
  142. data/lib/arel/crud.rb +2 -0
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/binary.rb +1 -1
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/node.rb +1 -1
  147. data/lib/arel/nodes/sql_literal.rb +1 -1
  148. data/lib/arel/nodes/update_statement.rb +4 -2
  149. data/lib/arel/predications.rb +1 -3
  150. data/lib/arel/select_manager.rb +6 -2
  151. data/lib/arel/table.rb +3 -7
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -0
  154. data/lib/arel/visitors/to_sql.rb +3 -1
  155. metadata +11 -15
  156. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -3,6 +3,38 @@
3
3
  module ActiveRecord
4
4
  module AttributeMethods
5
5
  # = Active Record Attribute Methods \Query
6
+ #
7
+ # Adds query methods for attributes that return either +true+ or +false+
8
+ # depending on the attribute type and value.
9
+ #
10
+ # For Boolean attributes this will return +true+ if the value is present
11
+ # and return +false+ otherwise:
12
+ #
13
+ # class Product < ActiveRecord::Base
14
+ # end
15
+ #
16
+ # product = Product.new(archived: false)
17
+ # product.archived? # => false
18
+ # product.archived = true
19
+ # product.archived? # => true
20
+ #
21
+ # For Numeric attributes this will return +true+ if the value is a non-zero
22
+ # number and return +false+ otherwise:
23
+ #
24
+ # product.inventory_count = 0
25
+ # product.inventory_count? # => false
26
+ # product.inventory_count = 1
27
+ # product.inventory_count? # => true
28
+ #
29
+ # For other attributes it will return +true+ if the value is present
30
+ # and return +false+ otherwise:
31
+ #
32
+ # product.name = nil
33
+ # product.name? # => false
34
+ # product.name = " "
35
+ # product.name? # => false
36
+ # product.name = "Orange"
37
+ # product.name? # => true
6
38
  module Query
7
39
  extend ActiveSupport::Concern
8
40
 
@@ -10,6 +42,8 @@ module ActiveRecord
10
42
  attribute_method_suffix "?", parameters: false
11
43
  end
12
44
 
45
+ # Returns +true+ or +false+ for the attribute identified by +attr_name+,
46
+ # depending on the attribute type and value.
13
47
  def query_attribute(attr_name)
14
48
  value = self.public_send(attr_name)
15
49
 
@@ -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
  #
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  elsif value.respond_to?(:infinite?) && value.infinite?
29
29
  value
30
30
  else
31
- map_avoiding_infinite_recursion(super) { |v| cast(v) }
31
+ map(super) { |v| cast(v) }
32
32
  end
33
33
  end
34
34
 
@@ -45,23 +45,13 @@ module ActiveRecord
45
45
  elsif value.respond_to?(:infinite?) && value.infinite?
46
46
  value
47
47
  else
48
- map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
48
+ map(value) { |v| convert_time_to_time_zone(v) }
49
49
  end
50
50
  end
51
51
 
52
52
  def set_time_zone_without_conversion(value)
53
53
  ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
54
54
  end
55
-
56
- def map_avoiding_infinite_recursion(value)
57
- map(value) do |v|
58
- if value.equal?(v)
59
- nil
60
- else
61
- yield(v)
62
- end
63
- end
64
- end
65
55
  end
66
56
 
67
57
  extend ActiveSupport::Concern
@@ -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)
87
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
88
88
  old_name = old_name.to_s
89
89
 
90
90
  if !abstract_class? && !has_attribute?(old_name)
@@ -113,13 +113,14 @@ 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")
116
+ alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
117
117
  end
118
118
 
119
- @attribute_methods_generated = true
120
-
121
119
  generate_alias_attributes
120
+
121
+ @attribute_methods_generated = true
122
122
  end
123
+
123
124
  true
124
125
  end
125
126
 
@@ -472,23 +473,27 @@ module ActiveRecord
472
473
  end
473
474
 
474
475
  def method_missing(name, ...)
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
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
489
487
  end
490
488
 
491
- super
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
492
497
  end
493
498
 
494
499
  def attribute_method?(attr_name)
@@ -21,28 +21,34 @@ 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
- # +name+ The name of the methods to define attribute methods for, and the
25
- # column which this will persist to.
24
+ # ==== Parameters
26
25
  #
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.
26
+ # [+name+]
27
+ # The name of the methods to define attribute methods for, and the
28
+ # column which this will persist to.
32
29
  #
33
- # ==== Options
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.
34
36
  #
35
- # The following options are accepted:
37
+ # ==== Options
36
38
  #
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+.
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+.
40
44
  #
41
- # +array+ (PostgreSQL only) specifies that the type should be an array (see the
42
- # examples below).
45
+ # [+:array+]
46
+ # (PostgreSQL only) Specifies that the type should be an array. See the
47
+ # examples below.
43
48
  #
44
- # +range+ (PostgreSQL only) specifies that the type should be a range (see the
45
- # examples below).
49
+ # [+:range+]
50
+ # (PostgreSQL only) Specifies that the type should be a range. See the
51
+ # examples below.
46
52
  #
47
53
  # When using a symbol for +cast_type+, extra options are forwarded to the
48
54
  # constructor of the type object.
@@ -178,8 +184,8 @@ module ActiveRecord
178
184
  # @currency_converter = currency_converter
179
185
  # end
180
186
  #
181
- # # value will be the result of +deserialize+ or
182
- # # +cast+. Assumed to be an instance of +Money+ in
187
+ # # value will be the result of #deserialize or
188
+ # # #cast. Assumed to be an instance of Money in
183
189
  # # this case.
184
190
  # def serialize(value)
185
191
  # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
@@ -217,17 +223,22 @@ module ActiveRecord
217
223
  # is provided so it can be used by plugin authors, application code
218
224
  # should probably use ClassMethods#attribute.
219
225
  #
220
- # +name+ The name of the attribute being defined. Expected to be a +String+.
226
+ # ==== Parameters
227
+ #
228
+ # [+name+]
229
+ # The name of the attribute being defined. Expected to be a +String+.
221
230
  #
222
- # +cast_type+ The type object to use for this attribute.
231
+ # [+cast_type+]
232
+ # The type object to use for this attribute.
223
233
  #
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.
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.
228
239
  #
229
- # +user_provided_default+ Whether the default value should be cast using
230
- # +cast+ or +deserialize+.
240
+ # [+user_provided_default+]
241
+ # Whether the default value should be cast using +cast+ or +deserialize+.
231
242
  def define_attribute(
232
243
  name,
233
244
  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,19 +372,29 @@ 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?
375
+ return true if record.valid?(context)
341
376
 
342
- unless valid = record.valid?(context)
343
- if association.options[:autosave]
344
- record.errors.each { |error|
345
- self.errors.objects.append(
346
- Associations::NestedError.new(association, error)
347
- )
348
- }
349
- else
350
- errors.add(association.reflection.name)
351
- end
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) }
352
383
  end
353
- valid
384
+
385
+ if association.options[:autosave]
386
+ return if equal?(record)
387
+
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)
395
+ end
396
+
397
+ errors.any?
354
398
  end
355
399
 
356
400
  # Is used as an around_save callback to check while saving a collection
@@ -431,33 +475,34 @@ module ActiveRecord
431
475
  return unless association && association.loaded?
432
476
 
433
477
  record = association.load_target
478
+ return unless record && !record.destroyed?
434
479
 
435
- if record && !record.destroyed?
436
- autosave = reflection.options[:autosave]
437
-
438
- if autosave && record.marked_for_destruction?
439
- record.destroy
440
- elsif autosave != false
441
- primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
442
- primary_key_value = primary_key.map { |key| _read_attribute(key) }
480
+ autosave = reflection.options[:autosave]
443
481
 
444
- if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
445
- unless reflection.through_reflection
446
- foreign_key = Array(reflection.foreign_key)
447
- 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)
448
488
 
449
- primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
450
- association_id = _read_attribute(primary_key)
451
- record[foreign_key] = association_id unless record[foreign_key] == association_id
452
- end
453
- association.set_inverse_instance(record)
454
- end
489
+ unless reflection.through_reflection
490
+ foreign_key = Array(reflection.foreign_key)
491
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
455
492
 
456
- saved = record.save(validate: !autosave)
457
- raise ActiveRecord::Rollback if !saved && autosave
458
- 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
459
496
  end
497
+ association.set_inverse_instance(record)
460
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
461
506
  end
462
507
  end
463
508
 
@@ -482,8 +527,7 @@ module ActiveRecord
482
527
  return false unless reflection.inverse_of&.polymorphic?
483
528
 
484
529
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
485
-
486
- reflection.active_record != record.class.polymorphic_class_for(class_name)
530
+ reflection.active_record.polymorphic_name != class_name
487
531
  end
488
532
 
489
533
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -502,7 +546,15 @@ module ActiveRecord
502
546
  foreign_key.each { |key| self[key] = nil }
503
547
  record.destroy
504
548
  elsif autosave != false
505
- 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
506
558
 
507
559
  if association.updated?
508
560
  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: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.
@@ -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,19 +54,22 @@ 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
- class StringConnectionName # :nodoc:
59
- attr_reader :name
60
-
61
- def initialize(name)
57
+ class ConnectionDescriptor # :nodoc:
58
+ def initialize(name, primary = false)
62
59
  @name = name
60
+ @primary = primary
61
+ end
62
+
63
+ def name
64
+ primary_class? ? "ActiveRecord::Base" : @name
63
65
  end
64
66
 
65
67
  def primary_class?
66
- false
68
+ @primary
67
69
  end
68
70
 
69
71
  def current_preventing_writes
70
- false
72
+ ActiveRecord::Base.preventing_writes?(@name)
71
73
  end
72
74
  end
73
75
 
@@ -116,7 +118,7 @@ module ActiveRecord
116
118
  pool_config = resolve_pool_config(config, owner_name, role, shard)
117
119
  db_config = pool_config.db_config
118
120
 
119
- pool_manager = set_pool_manager(pool_config.connection_name)
121
+ pool_manager = set_pool_manager(pool_config.connection_descriptor)
120
122
 
121
123
  # If there is an existing pool with the same values as the pool_config
122
124
  # don't remove the connection. Connections should only be removed if we are
@@ -128,8 +130,8 @@ module ActiveRecord
128
130
  # Update the pool_config's connection class if it differs. This is used
129
131
  # for ensuring that ActiveRecord::Base and the primary_abstract_class use
130
132
  # the same pool. Without this granular swapping will not work correctly.
131
- if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
132
- 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
133
135
  end
134
136
 
135
137
  existing_pool_config.pool
@@ -138,7 +140,7 @@ module ActiveRecord
138
140
  pool_manager.set_pool_config(role, shard, pool_config)
139
141
 
140
142
  payload = {
141
- connection_name: pool_config.connection_name,
143
+ connection_name: pool_config.connection_descriptor.name,
142
144
  role: role,
143
145
  shard: shard,
144
146
  config: db_config.configuration_hash
@@ -166,7 +168,7 @@ module ActiveRecord
166
168
  end
167
169
  end
168
170
 
169
- # Clears the cache which maps classes.
171
+ # Clears reloadable connection caches in all connection pools.
170
172
  #
171
173
  # See ConnectionPool#clear_reloadable_connections! for details.
172
174
  def clear_reloadable_connections!(role = nil)
@@ -210,18 +212,25 @@ module ActiveRecord
210
212
  # This makes retrieving the connection pool O(1) once the process is warm.
211
213
  # When a connection is established or removed, we invalidate the cache.
212
214
  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
215
+ pool_manager = get_pool_manager(connection_name)
216
+ pool = pool_manager&.get_pool_config(role, shard)&.pool
214
217
 
215
218
  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
219
+ selector = [
220
+ ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
221
+ ("'#{role}' role" unless role == ActiveRecord::Base.default_role),
222
+ ].compact.join(" and ")
223
+
224
+ selector = [
225
+ (connection_name unless connection_name == "ActiveRecord::Base"),
226
+ selector.presence,
227
+ ].compact.join(" with ")
228
+
229
+ selector = " for #{selector}" if selector.present?
230
+
231
+ message = "No database connection defined#{selector}."
223
232
 
224
- raise ConnectionNotEstablished, message
233
+ raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
225
234
  end
226
235
 
227
236
  pool
@@ -236,8 +245,8 @@ module ActiveRecord
236
245
  end
237
246
 
238
247
  # Get the existing pool manager or initialize and assign a new one.
239
- def set_pool_manager(connection_name)
240
- connection_name_to_pool_manager[connection_name] ||= PoolManager.new
248
+ def set_pool_manager(connection_descriptor)
249
+ connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
241
250
  end
242
251
 
243
252
  def pool_managers
@@ -272,9 +281,9 @@ module ActiveRecord
272
281
 
273
282
  def determine_owner_name(owner_name, config)
274
283
  if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
275
- StringConnectionName.new(owner_name.to_s)
284
+ ConnectionDescriptor.new(owner_name.to_s)
276
285
  elsif config.is_a?(Symbol)
277
- StringConnectionName.new(config.to_s)
286
+ ConnectionDescriptor.new(config.to_s)
278
287
  else
279
288
  owner_name
280
289
  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
@@ -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