activerecord 7.1.3.4 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +652 -2032
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +18 -11
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +11 -5
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +3 -3
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/has_one_association.rb +2 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  21. data/lib/active_record/associations/join_dependency.rb +10 -12
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +2 -1
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +6 -0
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +62 -289
  29. data/lib/active_record/attribute_assignment.rb +0 -2
  30. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  32. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  33. data/lib/active_record/attribute_methods/read.rb +4 -16
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  36. data/lib/active_record/attribute_methods/write.rb +3 -3
  37. data/lib/active_record/attribute_methods.rb +89 -58
  38. data/lib/active_record/attributes.rb +61 -47
  39. data/lib/active_record/autosave_association.rb +17 -31
  40. data/lib/active_record/base.rb +2 -3
  41. data/lib/active_record/callbacks.rb +1 -1
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  48. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
  53. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  54. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  60. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  61. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  63. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  67. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  72. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  73. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  74. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  75. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  78. data/lib/active_record/connection_adapters.rb +121 -0
  79. data/lib/active_record/connection_handling.rb +56 -41
  80. data/lib/active_record/core.rb +93 -40
  81. data/lib/active_record/counter_cache.rb +23 -10
  82. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  83. data/lib/active_record/database_configurations/database_config.rb +19 -4
  84. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  85. data/lib/active_record/database_configurations/url_config.rb +20 -1
  86. data/lib/active_record/database_configurations.rb +1 -1
  87. data/lib/active_record/delegated_type.rb +30 -6
  88. data/lib/active_record/destroy_association_async_job.rb +1 -1
  89. data/lib/active_record/dynamic_matchers.rb +2 -2
  90. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  91. data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
  92. data/lib/active_record/encryption/encryptor.rb +18 -3
  93. data/lib/active_record/encryption/key_provider.rb +1 -1
  94. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  95. data/lib/active_record/encryption/message_serializer.rb +4 -0
  96. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  97. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  98. data/lib/active_record/encryption/scheme.rb +8 -4
  99. data/lib/active_record/encryption.rb +2 -0
  100. data/lib/active_record/enum.rb +19 -2
  101. data/lib/active_record/errors.rb +46 -20
  102. data/lib/active_record/explain.rb +13 -24
  103. data/lib/active_record/fixtures.rb +37 -31
  104. data/lib/active_record/future_result.rb +17 -4
  105. data/lib/active_record/gem_version.rb +3 -3
  106. data/lib/active_record/inheritance.rb +4 -2
  107. data/lib/active_record/insert_all.rb +18 -15
  108. data/lib/active_record/integration.rb +4 -1
  109. data/lib/active_record/internal_metadata.rb +48 -34
  110. data/lib/active_record/locking/optimistic.rb +8 -7
  111. data/lib/active_record/log_subscriber.rb +0 -21
  112. data/lib/active_record/marshalling.rb +4 -1
  113. data/lib/active_record/message_pack.rb +2 -2
  114. data/lib/active_record/migration/command_recorder.rb +2 -3
  115. data/lib/active_record/migration/compatibility.rb +11 -3
  116. data/lib/active_record/migration/default_strategy.rb +4 -5
  117. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  118. data/lib/active_record/migration.rb +85 -76
  119. data/lib/active_record/model_schema.rb +38 -70
  120. data/lib/active_record/nested_attributes.rb +24 -5
  121. data/lib/active_record/normalization.rb +3 -7
  122. data/lib/active_record/persistence.rb +32 -354
  123. data/lib/active_record/query_cache.rb +19 -8
  124. data/lib/active_record/query_logs.rb +15 -0
  125. data/lib/active_record/query_logs_formatter.rb +1 -1
  126. data/lib/active_record/querying.rb +21 -9
  127. data/lib/active_record/railtie.rb +50 -68
  128. data/lib/active_record/railties/controller_runtime.rb +13 -4
  129. data/lib/active_record/railties/databases.rake +42 -45
  130. data/lib/active_record/reflection.rb +106 -38
  131. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  132. data/lib/active_record/relation/batches.rb +14 -8
  133. data/lib/active_record/relation/calculations.rb +96 -63
  134. data/lib/active_record/relation/delegation.rb +8 -11
  135. data/lib/active_record/relation/finder_methods.rb +16 -2
  136. data/lib/active_record/relation/merger.rb +4 -6
  137. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  138. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  139. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  140. data/lib/active_record/relation/predicate_builder.rb +3 -3
  141. data/lib/active_record/relation/query_methods.rb +245 -65
  142. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  143. data/lib/active_record/relation/spawn_methods.rb +2 -18
  144. data/lib/active_record/relation/where_clause.rb +7 -19
  145. data/lib/active_record/relation.rb +500 -66
  146. data/lib/active_record/result.rb +32 -45
  147. data/lib/active_record/runtime_registry.rb +39 -0
  148. data/lib/active_record/sanitization.rb +24 -19
  149. data/lib/active_record/schema.rb +8 -6
  150. data/lib/active_record/schema_dumper.rb +19 -9
  151. data/lib/active_record/schema_migration.rb +30 -14
  152. data/lib/active_record/scoping/named.rb +1 -0
  153. data/lib/active_record/signed_id.rb +20 -1
  154. data/lib/active_record/statement_cache.rb +7 -7
  155. data/lib/active_record/table_metadata.rb +1 -10
  156. data/lib/active_record/tasks/database_tasks.rb +98 -48
  157. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  158. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  159. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  160. data/lib/active_record/test_fixtures.rb +87 -89
  161. data/lib/active_record/testing/query_assertions.rb +121 -0
  162. data/lib/active_record/timestamp.rb +5 -3
  163. data/lib/active_record/token_for.rb +22 -12
  164. data/lib/active_record/touch_later.rb +1 -1
  165. data/lib/active_record/transaction.rb +132 -0
  166. data/lib/active_record/transactions.rb +70 -14
  167. data/lib/active_record/translation.rb +0 -2
  168. data/lib/active_record/type/serialized.rb +1 -3
  169. data/lib/active_record/type_caster/connection.rb +4 -4
  170. data/lib/active_record/validations/associated.rb +9 -3
  171. data/lib/active_record/validations/uniqueness.rb +15 -10
  172. data/lib/active_record/validations.rb +4 -1
  173. data/lib/active_record.rb +150 -41
  174. data/lib/arel/alias_predication.rb +1 -1
  175. data/lib/arel/collectors/bind.rb +2 -0
  176. data/lib/arel/collectors/composite.rb +7 -0
  177. data/lib/arel/collectors/sql_string.rb +1 -1
  178. data/lib/arel/collectors/substitute_binds.rb +1 -1
  179. data/lib/arel/nodes/binary.rb +0 -6
  180. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  181. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  182. data/lib/arel/nodes/node.rb +4 -3
  183. data/lib/arel/nodes/sql_literal.rb +7 -0
  184. data/lib/arel/nodes.rb +2 -2
  185. data/lib/arel/predications.rb +1 -1
  186. data/lib/arel/select_manager.rb +1 -1
  187. data/lib/arel/tree_manager.rb +8 -3
  188. data/lib/arel/update_manager.rb +2 -1
  189. data/lib/arel/visitors/dot.rb +1 -0
  190. data/lib/arel/visitors/mysql.rb +9 -4
  191. data/lib/arel/visitors/postgresql.rb +1 -12
  192. data/lib/arel/visitors/sqlite.rb +25 -0
  193. data/lib/arel/visitors/to_sql.rb +31 -17
  194. data/lib/arel.rb +7 -3
  195. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  196. metadata +21 -15
@@ -6,12 +6,13 @@ module ActiveRecord
6
6
  # See ActiveRecord::Attributes::ClassMethods for documentation
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeRegistration
9
10
 
10
- included do
11
- class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
- end
13
11
  # = Active Record \Attributes
14
12
  module ClassMethods
13
+ # :method: attribute
14
+ # :call-seq: attribute(name, cast_type = nil, **options)
15
+ #
15
16
  # Defines an attribute with a type on this model. It will override the
16
17
  # type of existing attributes if needed. This allows control over how
17
18
  # values are converted to and from SQL when assigned to a model. It also
@@ -24,15 +25,17 @@ module ActiveRecord
24
25
  # column which this will persist to.
25
26
  #
26
27
  # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
27
- # to be used for this attribute. See the examples below for more
28
- # information about providing custom type objects.
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.
29
32
  #
30
33
  # ==== Options
31
34
  #
32
35
  # The following options are accepted:
33
36
  #
34
37
  # +default+ The default value to use when no value is provided. If this option
35
- # is not passed, the previous default value (if any) will be used.
38
+ # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
36
39
  # Otherwise, the default will be +nil+.
37
40
  #
38
41
  # +array+ (PostgreSQL only) specifies that the type should be an array (see the
@@ -134,7 +137,7 @@ module ActiveRecord
134
137
  # expected API. It is recommended that your type objects inherit from an
135
138
  # existing type, or from ActiveRecord::Type::Value
136
139
  #
137
- # class MoneyType < ActiveRecord::Type::Integer
140
+ # class PriceType < ActiveRecord::Type::Integer
138
141
  # def cast(value)
139
142
  # if !value.kind_of?(Numeric) && value.include?('$')
140
143
  # price_in_dollars = value.gsub(/\$/, '').to_f
@@ -146,11 +149,11 @@ module ActiveRecord
146
149
  # end
147
150
  #
148
151
  # # config/initializers/types.rb
149
- # ActiveRecord::Type.register(:money, MoneyType)
152
+ # ActiveRecord::Type.register(:price, PriceType)
150
153
  #
151
154
  # # app/models/store_listing.rb
152
155
  # class StoreListing < ActiveRecord::Base
153
- # attribute :price_in_cents, :money
156
+ # attribute :price_in_cents, :price
154
157
  # end
155
158
  #
156
159
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
@@ -170,7 +173,7 @@ module ActiveRecord
170
173
  # class Money < Struct.new(:amount, :currency)
171
174
  # end
172
175
  #
173
- # class MoneyType < ActiveRecord::Type::Value
176
+ # class PriceType < ActiveRecord::Type::Value
174
177
  # def initialize(currency_converter:)
175
178
  # @currency_converter = currency_converter
176
179
  # end
@@ -185,12 +188,12 @@ module ActiveRecord
185
188
  # end
186
189
  #
187
190
  # # config/initializers/types.rb
188
- # ActiveRecord::Type.register(:money, MoneyType)
191
+ # ActiveRecord::Type.register(:price, PriceType)
189
192
  #
190
193
  # # app/models/product.rb
191
194
  # class Product < ActiveRecord::Base
192
195
  # currency_converter = ConversionRatesFromTheInternet.new
193
- # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
196
+ # attribute :price_in_bitcoins, :price, currency_converter: currency_converter
194
197
  # end
195
198
  #
196
199
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
@@ -205,37 +208,12 @@ module ActiveRecord
205
208
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
206
209
  # will be called from ActiveModel::Dirty. See the documentation for those
207
210
  # methods in ActiveModel::Type::Value for more details.
208
- def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
209
- name = name.to_s
210
- name = attribute_aliases[name] || name
211
-
212
- reload_schema_from_cache
213
-
214
- case cast_type
215
- when Symbol
216
- cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
217
- when nil
218
- if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
219
- default = prev_default if default == NO_DEFAULT_PROVIDED
220
- else
221
- prev_cast_type = -> subtype { subtype }
222
- end
223
-
224
- cast_type = if block_given?
225
- -> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
226
- else
227
- prev_cast_type
228
- end
229
- end
230
-
231
- self.attributes_to_define_after_schema_loads =
232
- attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
233
- end
211
+ #
212
+ #--
213
+ # Implemented by ActiveModel::AttributeRegistration#attribute.
234
214
 
235
- # This is the low level API which sits beneath +attribute+. It only
236
- # accepts type objects, and will do its work immediately instead of
237
- # waiting for the schema to load. Automatic schema detection and
238
- # ClassMethods#attribute both call this under the hood. While this method
215
+ # This API only accepts type objects, and will do its work immediately instead of
216
+ # waiting for the schema to load. While this method
239
217
  # is provided so it can be used by plugin authors, application code
240
218
  # should probably use ClassMethods#attribute.
241
219
  #
@@ -260,14 +238,38 @@ module ActiveRecord
260
238
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
261
239
  end
262
240
 
263
- def load_schema! # :nodoc:
264
- super
265
- attributes_to_define_after_schema_loads.each do |name, (cast_type, default)|
266
- cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
267
- define_attribute(name, cast_type, default: default)
241
+ def _default_attributes # :nodoc:
242
+ @default_attributes ||= begin
243
+ attributes_hash = with_connection do |connection|
244
+ columns_hash.transform_values do |column|
245
+ ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
246
+ end
247
+ end
248
+
249
+ attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
250
+ apply_pending_attribute_modifications(attribute_set)
251
+ attribute_set
268
252
  end
269
253
  end
270
254
 
255
+ ##
256
+ # :method: type_for_attribute
257
+ # :call-seq: type_for_attribute(attribute_name, &block)
258
+ #
259
+ # See ActiveModel::Attributes::ClassMethods#type_for_attribute.
260
+ #
261
+ # This method will access the database and load the model's schema if
262
+ # necessary.
263
+ #--
264
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
265
+
266
+ ##
267
+ protected
268
+ def reload_schema_from_cache(*)
269
+ reset_default_attributes!
270
+ super
271
+ end
272
+
271
273
  private
272
274
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
273
275
  private_constant :NO_DEFAULT_PROVIDED
@@ -287,6 +289,18 @@ module ActiveRecord
287
289
  end
288
290
  _default_attributes[name] = default_attribute
289
291
  end
292
+
293
+ def reset_default_attributes
294
+ reload_schema_from_cache
295
+ end
296
+
297
+ def resolve_type_name(name, **options)
298
+ Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
299
+ end
300
+
301
+ def type_for_column(connection, column)
302
+ hook_attribute_type(column.name, super)
303
+ end
290
304
  end
291
305
  end
292
306
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/associations/nested_error"
4
+
3
5
  module ActiveRecord
4
6
  # = Active Record Autosave Association
5
7
  #
@@ -315,7 +317,7 @@ module ActiveRecord
315
317
  def validate_single_association(reflection)
316
318
  association = association_instance_get(reflection.name)
317
319
  record = association && association.reader
318
- association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
320
+ association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
319
321
  end
320
322
 
321
323
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +326,7 @@ module ActiveRecord
324
326
  def validate_collection_association(reflection)
325
327
  if association = association_instance_get(reflection.name)
326
328
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
327
- records.each_with_index { |record, index| association_valid?(reflection, record, index) }
329
+ records.each { |record| association_valid?(association, record) }
328
330
  end
329
331
  end
330
332
  end
@@ -332,40 +334,25 @@ module ActiveRecord
332
334
  # Returns whether or not the association is valid and applies any errors to
333
335
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
334
336
  # enabled records if they're marked_for_destruction? or destroyed.
335
- def association_valid?(reflection, record, index = nil)
336
- return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
337
+ def association_valid?(association, record)
338
+ return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
337
339
 
338
340
  context = validation_context if custom_validation_context?
339
341
 
340
342
  unless valid = record.valid?(context)
341
- if reflection.options[:autosave]
342
- indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord.index_nested_attribute_errors)
343
-
344
- record.errors.group_by_attribute.each { |attribute, errors|
345
- attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
346
-
347
- errors.each { |error|
348
- self.errors.import(
349
- error,
350
- attribute: attribute
351
- )
352
- }
343
+ if association.options[:autosave]
344
+ record.errors.each { |error|
345
+ self.errors.objects.append(
346
+ Associations::NestedError.new(association, error)
347
+ )
353
348
  }
354
349
  else
355
- errors.add(reflection.name)
350
+ errors.add(association.reflection.name)
356
351
  end
357
352
  end
358
353
  valid
359
354
  end
360
355
 
361
- def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
362
- if indexed_attribute
363
- "#{reflection.name}[#{index}].#{attribute}"
364
- else
365
- "#{reflection.name}.#{attribute}"
366
- end
367
- end
368
-
369
356
  # Is used as an around_save callback to check while saving a collection
370
357
  # association whether or not the parent was a new record before saving.
371
358
  def around_save_collection_association
@@ -441,7 +428,9 @@ module ActiveRecord
441
428
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
442
429
  def save_has_one_association(reflection)
443
430
  association = association_instance_get(reflection.name)
444
- record = association && association.load_target
431
+ return unless association && association.loaded?
432
+
433
+ record = association.load_target
445
434
 
446
435
  if record && !record.destroyed?
447
436
  autosave = reflection.options[:autosave]
@@ -458,7 +447,8 @@ module ActiveRecord
458
447
  primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
459
448
 
460
449
  primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
461
- record[foreign_key] = _read_attribute(primary_key)
450
+ association_id = _read_attribute(primary_key)
451
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
462
452
  end
463
453
  association.set_inverse_instance(record)
464
454
  end
@@ -547,10 +537,6 @@ module ActiveRecord
547
537
  end
548
538
  end
549
539
 
550
- def custom_validation_context?
551
- validation_context && [:create, :update].exclude?(validation_context)
552
- end
553
-
554
540
  def _ensure_no_duplicate_errors
555
541
  errors.uniq!
556
542
  end
@@ -233,7 +233,7 @@ module ActiveRecord # :nodoc:
233
233
  #
234
234
  # Connections are usually created through
235
235
  # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
236
- # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
236
+ # by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this
237
237
  # connection. But you can also set a class-specific connection. For example, if Course is an
238
238
  # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
239
239
  # and Course and all of its subclasses will use this connection instead.
@@ -280,7 +280,7 @@ module ActiveRecord # :nodoc:
280
280
  # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
281
281
  # instances in the current object space.
282
282
  class Base
283
- extend ActiveModel::Naming
283
+ include ActiveModel::API
284
284
 
285
285
  extend ActiveSupport::Benchmarkable
286
286
  extend ActiveSupport::DescendantsTracker
@@ -304,7 +304,6 @@ module ActiveRecord # :nodoc:
304
304
  include Scoping
305
305
  include Sanitization
306
306
  include AttributeAssignment
307
- include ActiveModel::Conversion
308
307
  include Integration
309
308
  include Validations
310
309
  include CounterCache
@@ -418,7 +418,7 @@ module ActiveRecord
418
418
 
419
419
  def destroy # :nodoc:
420
420
  @_destroy_callback_already_called ||= false
421
- return if @_destroy_callback_already_called
421
+ return true if @_destroy_callback_already_called
422
422
  @_destroy_callback_already_called = true
423
423
  _run_destroy_callbacks { super }
424
424
  rescue RecordNotDestroyed => e
@@ -55,9 +55,6 @@ module ActiveRecord
55
55
  # about the model. The model needs to pass a connection specification name to the handler,
56
56
  # in order to look up the correct connection pool.
57
57
  class ConnectionHandler
58
- FINALIZER = lambda { |_| ActiveSupport::ForkTracker.check! }
59
- private_constant :FINALIZER
60
-
61
58
  class StringConnectionName # :nodoc:
62
59
  attr_reader :name
63
60
 
@@ -77,9 +74,6 @@ module ActiveRecord
77
74
  def initialize
78
75
  # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name).
79
76
  @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2)
80
-
81
- # Backup finalizer: if the forked child skipped Kernel#fork the early discard has not occurred
82
- ObjectSpace.define_finalizer self, FINALIZER
83
77
  end
84
78
 
85
79
  def prevent_writes # :nodoc:
@@ -94,22 +88,10 @@ module ActiveRecord
94
88
  connection_name_to_pool_manager.keys
95
89
  end
96
90
 
97
- def all_connection_pools
98
- ActiveRecord.deprecator.warn(<<-MSG.squish)
99
- The `all_connection_pools` method is deprecated in favor of `connection_pool_list`.
100
- Call `connection_pool_list(:all)` to get the same behavior as `all_connection_pools`.
101
- MSG
102
- connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
103
- end
104
-
105
- # Returns the pools for a connection handler and given role. If +:all+ is passed,
91
+ # Returns the pools for a connection handler and given role. If +:all+ is passed,
106
92
  # all pools belonging to the connection handler will be returned.
107
93
  def connection_pool_list(role = nil)
108
- if role.nil?
109
- deprecation_for_pool_handling(__method__)
110
- role = ActiveRecord::Base.current_role
111
- connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
112
- elsif role == :all
94
+ if role.nil? || role == :all
113
95
  connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) }
114
96
  else
115
97
  connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) }
@@ -171,11 +153,6 @@ module ActiveRecord
171
153
  # Returns true if there are any active connections among the connection
172
154
  # pools that the ConnectionHandler is managing.
173
155
  def active_connections?(role = nil)
174
- if role.nil?
175
- deprecation_for_pool_handling(__method__)
176
- role = ActiveRecord::Base.current_role
177
- end
178
-
179
156
  each_connection_pool(role).any?(&:active_connection?)
180
157
  end
181
158
 
@@ -183,32 +160,20 @@ module ActiveRecord
183
160
  # and also returns connections to the pool cached by threads that are no
184
161
  # longer alive.
185
162
  def clear_active_connections!(role = nil)
186
- if role.nil?
187
- deprecation_for_pool_handling(__method__)
188
- role = ActiveRecord::Base.current_role
163
+ each_connection_pool(role).each do |pool|
164
+ pool.release_connection
165
+ pool.disable_query_cache!
189
166
  end
190
-
191
- each_connection_pool(role).each(&:release_connection)
192
167
  end
193
168
 
194
169
  # Clears the cache which maps classes.
195
170
  #
196
171
  # See ConnectionPool#clear_reloadable_connections! for details.
197
172
  def clear_reloadable_connections!(role = nil)
198
- if role.nil?
199
- deprecation_for_pool_handling(__method__)
200
- role = ActiveRecord::Base.current_role
201
- end
202
-
203
173
  each_connection_pool(role).each(&:clear_reloadable_connections!)
204
174
  end
205
175
 
206
176
  def clear_all_connections!(role = nil)
207
- if role.nil?
208
- deprecation_for_pool_handling(__method__)
209
- role = ActiveRecord::Base.current_role
210
- end
211
-
212
177
  each_connection_pool(role).each(&:disconnect!)
213
178
  end
214
179
 
@@ -216,11 +181,6 @@ module ActiveRecord
216
181
  #
217
182
  # See ConnectionPool#flush! for details.
218
183
  def flush_idle_connections!(role = nil)
219
- if role.nil?
220
- deprecation_for_pool_handling(__method__)
221
- role = ActiveRecord::Base.current_role
222
- end
223
-
224
184
  each_connection_pool(role).each(&:flush!)
225
185
  end
226
186
 
@@ -229,21 +189,8 @@ module ActiveRecord
229
189
  # opened and set as the active connection for the class it was defined
230
190
  # for (not necessarily the current class).
231
191
  def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc:
232
- pool = retrieve_connection_pool(connection_name, role: role, shard: shard)
233
-
234
- unless pool
235
- if shard != ActiveRecord::Base.default_shard
236
- message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
237
- elsif role != ActiveRecord::Base.default_role
238
- message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
239
- else
240
- message = "No connection pool for '#{connection_name}' found."
241
- end
242
-
243
- raise ConnectionNotEstablished, message
244
- end
245
-
246
- pool.connection
192
+ pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true)
193
+ pool.lease_connection
247
194
  end
248
195
 
249
196
  # Returns true if a connection that's accessible to this class has
@@ -262,9 +209,22 @@ module ActiveRecord
262
209
  # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager.
263
210
  # This makes retrieving the connection pool O(1) once the process is warm.
264
211
  # When a connection is established or removed, we invalidate the cache.
265
- def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
266
- pool_config = get_pool_manager(connection_name)&.get_pool_config(role, shard)
267
- pool_config&.pool
212
+ def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
+ pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
214
+
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
223
+
224
+ raise ConnectionNotEstablished, message
225
+ end
226
+
227
+ pool
268
228
  end
269
229
 
270
230
  private
@@ -284,23 +244,6 @@ module ActiveRecord
284
244
  connection_name_to_pool_manager.values
285
245
  end
286
246
 
287
- def deprecation_for_pool_handling(method)
288
- roles = []
289
- pool_managers.each do |pool_manager|
290
- roles << pool_manager.role_names
291
- end
292
-
293
- if roles.flatten.uniq.count > 1
294
- ActiveRecord.deprecator.warn(<<-MSG.squish)
295
- `#{method}` currently only applies to connection pools in the current
296
- role (`#{ActiveRecord::Base.current_role}`). In Rails 7.2, this method
297
- will apply to all known pools, regardless of role. To affect only those
298
- connections belonging to a specific role, pass the role name as an
299
- argument. To switch to the new behavior, pass `:all` as the role name.
300
- MSG
301
- end
302
- end
303
-
304
247
  def disconnect_pool_from_pool_manager(pool_manager, role, shard)
305
248
  pool_config = pool_manager.remove_pool_config(role, shard)
306
249
 
@@ -322,34 +265,8 @@ module ActiveRecord
322
265
  #
323
266
  def resolve_pool_config(config, connection_name, role, shard)
324
267
  db_config = Base.configurations.resolve(config)
325
-
268
+ db_config.validate!
326
269
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
327
-
328
- # Require the adapter itself and give useful feedback about
329
- # 1. Missing adapter gems and
330
- # 2. Adapter gems' missing dependencies.
331
- path_to_adapter = "active_record/connection_adapters/#{db_config.adapter}_adapter"
332
- begin
333
- require path_to_adapter
334
- rescue LoadError => e
335
- # We couldn't require the adapter itself. Raise an exception that
336
- # points out config typos and missing gems.
337
- if e.path == path_to_adapter
338
- # We can assume that a non-builtin adapter was specified, so it's
339
- # either misspelled or missing from Gemfile.
340
- raise LoadError, "Could not load the '#{db_config.adapter}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
341
-
342
- # Bubbled up from the adapter require. Prefix the exception message
343
- # with some guidance about how to address it and reraise.
344
- else
345
- raise LoadError, "Error loading the '#{db_config.adapter}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
346
- end
347
- end
348
-
349
- unless ActiveRecord::Base.respond_to?(db_config.adapter_method)
350
- raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
351
- end
352
-
353
270
  ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard)
354
271
  end
355
272
 
@@ -43,6 +43,7 @@ module ActiveRecord
43
43
  # Advise multi-threaded app servers to ignore this thread for
44
44
  # the purposes of fork safety warnings
45
45
  Thread.current.thread_variable_set(:fork_safe, true)
46
+ Thread.current.name = "AR Pool Reaper"
46
47
  running = true
47
48
  while running
48
49
  sleep t