activerecord 7.2.2 → 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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +173 -920
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +25 -5
  5. data/lib/active_record/associations/builder/association.rb +7 -6
  6. data/lib/active_record/associations/collection_association.rb +10 -8
  7. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  8. data/lib/active_record/associations/has_many_through_association.rb +4 -9
  9. data/lib/active_record/associations/preloader/association.rb +2 -2
  10. data/lib/active_record/associations/singular_association.rb +8 -3
  11. data/lib/active_record/associations.rb +50 -32
  12. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  13. data/lib/active_record/autosave_association.rb +69 -27
  14. data/lib/active_record/callbacks.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  16. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  17. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +0 -27
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  21. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  23. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -6
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +23 -45
  27. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  28. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  29. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  30. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  31. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  32. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  33. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  34. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  35. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -8
  36. data/lib/active_record/connection_adapters/postgresql_adapter.rb +41 -93
  37. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  38. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  39. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  40. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  41. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
  43. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  44. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  45. data/lib/active_record/connection_handling.rb +22 -0
  46. data/lib/active_record/core.rb +7 -32
  47. data/lib/active_record/encryption/config.rb +3 -1
  48. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  49. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  50. data/lib/active_record/encryption/encryptor.rb +15 -8
  51. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  52. data/lib/active_record/encryption/scheme.rb +8 -1
  53. data/lib/active_record/errors.rb +13 -5
  54. data/lib/active_record/fixtures.rb +0 -1
  55. data/lib/active_record/future_result.rb +14 -10
  56. data/lib/active_record/gem_version.rb +4 -4
  57. data/lib/active_record/insert_all.rb +1 -1
  58. data/lib/active_record/marshalling.rb +1 -4
  59. data/lib/active_record/migration/command_recorder.rb +22 -5
  60. data/lib/active_record/migration/compatibility.rb +5 -2
  61. data/lib/active_record/migration.rb +35 -33
  62. data/lib/active_record/model_schema.rb +1 -1
  63. data/lib/active_record/nested_attributes.rb +4 -6
  64. data/lib/active_record/persistence.rb +128 -130
  65. data/lib/active_record/query_cache.rb +5 -4
  66. data/lib/active_record/query_logs.rb +98 -40
  67. data/lib/active_record/query_logs_formatter.rb +17 -28
  68. data/lib/active_record/querying.rb +6 -6
  69. data/lib/active_record/railtie.rb +3 -4
  70. data/lib/active_record/reflection.rb +9 -7
  71. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  72. data/lib/active_record/relation/batches.rb +132 -72
  73. data/lib/active_record/relation/calculations.rb +25 -20
  74. data/lib/active_record/relation/delegation.rb +25 -14
  75. data/lib/active_record/relation/finder_methods.rb +18 -18
  76. data/lib/active_record/relation/merger.rb +8 -8
  77. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  78. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  79. data/lib/active_record/relation/predicate_builder.rb +5 -0
  80. data/lib/active_record/relation/query_methods.rb +81 -75
  81. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  82. data/lib/active_record/relation/spawn_methods.rb +1 -1
  83. data/lib/active_record/relation.rb +72 -61
  84. data/lib/active_record/result.rb +68 -7
  85. data/lib/active_record/sanitization.rb +7 -6
  86. data/lib/active_record/schema_dumper.rb +5 -0
  87. data/lib/active_record/schema_migration.rb +2 -1
  88. data/lib/active_record/scoping/named.rb +5 -2
  89. data/lib/active_record/statement_cache.rb +12 -12
  90. data/lib/active_record/store.rb +7 -3
  91. data/lib/active_record/tasks/database_tasks.rb +24 -15
  92. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  93. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  94. data/lib/active_record/test_fixtures.rb +12 -0
  95. data/lib/active_record/testing/query_assertions.rb +2 -2
  96. data/lib/active_record/token_for.rb +1 -1
  97. data/lib/active_record/validations/uniqueness.rb +8 -8
  98. data/lib/active_record.rb +15 -0
  99. data/lib/arel/collectors/bind.rb +1 -1
  100. data/lib/arel/visitors/sqlite.rb +0 -25
  101. metadata +10 -10
data/README.rdoc CHANGED
@@ -139,7 +139,7 @@ A short rundown of some of the major features:
139
139
 
140
140
  * Database agnostic schema management with Migrations.
141
141
 
142
- class AddSystemSettings < ActiveRecord::Migration[7.2]
142
+ class AddSystemSettings < ActiveRecord::Migration[8.0]
143
143
  def up
144
144
  create_table :system_settings do |t|
145
145
  t.string :name
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
35
35
  class Association # :nodoc:
36
36
  attr_accessor :owner
37
- attr_reader :target, :reflection, :disable_joins
37
+ attr_reader :reflection, :disable_joins
38
38
 
39
39
  delegate :options, to: :reflection
40
40
 
@@ -50,6 +50,13 @@ module ActiveRecord
50
50
  @skip_strict_loading = nil
51
51
  end
52
52
 
53
+ def target
54
+ if @target.is_a?(Promise)
55
+ @target = @target.value
56
+ end
57
+ @target
58
+ end
59
+
53
60
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
54
61
  def reset
55
62
  @loaded = false
@@ -172,7 +179,7 @@ module ActiveRecord
172
179
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
173
180
  # not reraised. The proxy is \reset and +nil+ is the return value.
174
181
  def load_target
175
- @target = find_target if (@stale_state && stale_target?) || find_target?
182
+ @target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
176
183
 
177
184
  loaded! unless loaded?
178
185
  target
@@ -180,6 +187,13 @@ module ActiveRecord
180
187
  reset
181
188
  end
182
189
 
190
+ def async_load_target # :nodoc:
191
+ @target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
192
+
193
+ loaded! unless loaded?
194
+ nil
195
+ end
196
+
183
197
  # We can't dump @reflection and @through_reflection since it contains the scope proc
184
198
  def marshal_dump
185
199
  ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
@@ -223,13 +237,19 @@ module ActiveRecord
223
237
  klass
224
238
  end
225
239
 
226
- def find_target
240
+ def find_target(async: false)
227
241
  if violates_strict_loading?
228
242
  Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
229
243
  end
230
244
 
231
245
  scope = self.scope
232
- return scope.to_a if skip_statement_cache?(scope)
246
+ if skip_statement_cache?(scope)
247
+ if async
248
+ return scope.load_async.then(&:to_a)
249
+ else
250
+ return scope.to_a
251
+ end
252
+ end
233
253
 
234
254
  sc = reflection.association_scope_cache(klass, owner) do |params|
235
255
  as = AssociationScope.create { params.bind }
@@ -238,7 +258,7 @@ module ActiveRecord
238
258
 
239
259
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
240
260
  klass.with_connection do |c|
241
- sc.execute(binds, c) do |record|
261
+ sc.execute(binds, c, async: async) do |record|
242
262
  set_inverse_instance(record)
243
263
  if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
244
264
  record.strict_loading!
@@ -30,10 +30,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  end
31
31
 
32
32
  reflection = create_reflection(model, name, scope, options, &block)
33
- define_accessors model, reflection
34
- define_callbacks model, reflection
35
- define_validations model, reflection
36
- define_change_tracking_methods model, reflection
33
+ define_accessors(model, reflection)
34
+ define_callbacks(model, reflection)
35
+ define_validations(model, reflection)
36
+ define_change_tracking_methods(model, reflection)
37
37
  reflection
38
38
  end
39
39
 
@@ -71,6 +71,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
71
71
  end
72
72
 
73
73
  def self.define_extensions(model, name)
74
+ # noop
74
75
  end
75
76
 
76
77
  def self.define_callbacks(model, reflection)
@@ -81,7 +82,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
81
82
  end
82
83
 
83
84
  Association.extensions.each do |extension|
84
- extension.build model, reflection
85
+ extension.build(model, reflection)
85
86
  end
86
87
  end
87
88
 
@@ -131,7 +132,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
131
132
  err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
132
133
  raise ActiveRecord::ConfigurationError, err_message
133
134
  end
134
- unless valid_dependent_options.include? dependent
135
+ unless valid_dependent_options.include?(dependent)
135
136
  raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
136
137
  end
137
138
  end
@@ -94,7 +94,7 @@ module ActiveRecord
94
94
  def find(*args)
95
95
  if options[:inverse_of] && loaded?
96
96
  args_flatten = args.flatten
97
- model = scope.klass
97
+ model = scope.model
98
98
 
99
99
  if args_flatten.blank?
100
100
  error_message = "Couldn't find #{model.name} without an ID"
@@ -256,14 +256,16 @@ module ActiveRecord
256
256
  end
257
257
 
258
258
  def include?(record)
259
- if record.is_a?(reflection.klass)
260
- if record.new_record?
261
- include_in_memory?(record)
262
- else
263
- loaded? ? target.include?(record) : scope.exists?(record.id)
264
- end
259
+ klass = reflection.klass
260
+ return false unless record.is_a?(klass)
261
+
262
+ if record.new_record?
263
+ include_in_memory?(record)
264
+ elsif loaded?
265
+ target.include?(record)
265
266
  else
266
- false
267
+ record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
268
+ scope.exists?(record_id)
267
269
  end
268
270
  end
269
271
 
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
  end
48
48
 
49
49
  if scope.order_values.empty? && ordered
50
- split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
50
+ split_scope = DisableJoinsAssociationRelation.create(scope.model, key, join_ids)
51
51
  split_scope.where_clause += scope.where_clause
52
52
  split_scope
53
53
  else
@@ -93,13 +93,7 @@ module ActiveRecord
93
93
  @through_scope = scope
94
94
  record = super
95
95
 
96
- inverse =
97
- if source_reflection.polymorphic?
98
- source_reflection.polymorphic_inverse_of(record.class)
99
- else
100
- source_reflection.inverse_of
101
- end
102
-
96
+ inverse = source_reflection.inverse_of
103
97
  if inverse
104
98
  if inverse.collection?
105
99
  record.send(inverse.name) << build_through_record(record)
@@ -146,7 +140,7 @@ module ActiveRecord
146
140
 
147
141
  case method
148
142
  when :destroy
149
- if scope.klass.primary_key
143
+ if scope.model.primary_key
150
144
  count = scope.destroy_all.count(&:destroyed?)
151
145
  else
152
146
  scope.each(&:_run_destroy_callbacks)
@@ -222,7 +216,8 @@ module ActiveRecord
222
216
  end
223
217
  end
224
218
 
225
- def find_target
219
+ def find_target(async: false)
220
+ raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async
226
221
  return [] unless target_reflection_has_associated_record?
227
222
  return scope.to_a if disable_joins
228
223
  super
@@ -17,12 +17,12 @@ module ActiveRecord
17
17
  def eql?(other)
18
18
  association_key_name == other.association_key_name &&
19
19
  scope.table_name == other.scope.table_name &&
20
- scope.connection_specification_name == other.scope.connection_specification_name &&
20
+ scope.model.connection_specification_name == other.scope.model.connection_specification_name &&
21
21
  scope.values_for_queries == other.scope.values_for_queries
22
22
  end
23
23
 
24
24
  def hash
25
- [association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
25
+ [association_key_name, scope.model.table_name, scope.model.connection_specification_name, scope.values_for_queries].hash
26
26
  end
27
27
 
28
28
  def records_for(loaders)
@@ -18,6 +18,7 @@ module ActiveRecord
18
18
  def reset
19
19
  super
20
20
  @target = nil
21
+ @future_target = nil
21
22
  end
22
23
 
23
24
  # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
@@ -43,11 +44,15 @@ module ActiveRecord
43
44
  super.except!(*Array(klass.primary_key))
44
45
  end
45
46
 
46
- def find_target
47
+ def find_target(async: false)
47
48
  if disable_joins
48
- scope.first
49
+ if async
50
+ scope.load_async.then(&:first)
51
+ else
52
+ scope.first
53
+ end
49
54
  else
50
- super.first
55
+ super.then(&:first)
51
56
  end
52
57
  end
53
58
 
@@ -379,21 +379,43 @@ module ActiveRecord
379
379
  # after_add: :congratulate_client,
380
380
  # after_remove: :log_after_remove
381
381
  #
382
- # def congratulate_client(record)
382
+ # def congratulate_client(client)
383
383
  # # ...
384
384
  # end
385
385
  #
386
- # def log_after_remove(record)
386
+ # def log_after_remove(client)
387
387
  # # ...
388
388
  # end
389
389
  # end
390
390
  #
391
+ # Callbacks can be defined in three ways:
392
+ #
393
+ # 1. A symbol that references a method defined on the class with the
394
+ # associated collection. For example, <tt>after_add: :congratulate_client</tt>
395
+ # invokes <tt>Firm#congratulate_client(client)</tt>.
396
+ # 2. A callable with a signature that accepts both the record with the
397
+ # associated collection and the record being added or removed. For
398
+ # example, <tt>after_add: ->(firm, client) { ... }</tt>.
399
+ # 3. An object that responds to the callback name. For example, passing
400
+ # <tt>after_add: CallbackObject.new</tt> invokes <tt>CallbackObject#after_add(firm,
401
+ # client)</tt>.
402
+ #
391
403
  # It's possible to stack callbacks by passing them as an array. Example:
392
404
  #
405
+ # class CallbackObject
406
+ # def after_add(firm, client)
407
+ # firm.log << "after_adding #{client.id}"
408
+ # end
409
+ # end
410
+ #
393
411
  # class Firm < ActiveRecord::Base
394
412
  # has_many :clients,
395
413
  # dependent: :destroy,
396
- # after_add: [:congratulate_client, -> (firm, record) { firm.log << "after_adding#{record.id}" }],
414
+ # after_add: [
415
+ # :congratulate_client,
416
+ # -> (firm, client) { firm.log << "after_adding #{client.id}" },
417
+ # CallbackObject.new
418
+ # ],
397
419
  # after_remove: :log_after_remove
398
420
  # end
399
421
  #
@@ -537,7 +559,7 @@ module ActiveRecord
537
559
  # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
538
560
  # @group.avatars.delete(@group.avatars.last) # so would this
539
561
  #
540
- # === Setting Inverses
562
+ # == Setting Inverses
541
563
  #
542
564
  # If you are using a #belongs_to on the join model, it is a good idea to set the
543
565
  # <tt>:inverse_of</tt> option on the #belongs_to, which will mean that the following example
@@ -1199,11 +1221,8 @@ module ActiveRecord
1199
1221
  # If you are going to modify the association (rather than just read from it), then it is
1200
1222
  # a good idea to set the <tt>:inverse_of</tt> option on the source association on the
1201
1223
  # join model. This allows associated records to be built which will automatically create
1202
- # the appropriate join model records when they are saved. See
1203
- # {Association Join Models}[rdoc-ref:Associations::ClassMethods@Association+Join+Models]
1204
- # and {Setting Inverses}[rdoc-ref:Associations::ClassMethods@Setting+Inverses] for
1205
- # more detail.
1206
- #
1224
+ # the appropriate join model records when they are saved. (See the 'Association Join Models'
1225
+ # and 'Setting Inverses' sections above.)
1207
1226
  # [+:disable_joins+]
1208
1227
  # Specifies whether joins should be skipped for an association. If set to true, two or more queries
1209
1228
  # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory
@@ -1232,8 +1251,7 @@ module ActiveRecord
1232
1251
  # [+:inverse_of+]
1233
1252
  # Specifies the name of the #belongs_to association on the associated object
1234
1253
  # that is the inverse of this #has_many association.
1235
- # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations]
1236
- # for more detail.
1254
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
1237
1255
  # [+:extend+]
1238
1256
  # Specifies a module or array of modules that will be extended into the association object returned.
1239
1257
  # Useful for defining methods on associations, especially when they should be shared between multiple
@@ -1255,6 +1273,14 @@ module ActiveRecord
1255
1273
  # persisted new records placed at the end.
1256
1274
  # When set to +:nested_attributes_order+, the index is based on the record order received by
1257
1275
  # nested attributes setter, when accepts_nested_attributes_for is used.
1276
+ # [:before_add]
1277
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is added</b> to the association collection.
1278
+ # [:after_add]
1279
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is added</b> to the association collection.
1280
+ # [:before_remove]
1281
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is removed</b> from the association collection.
1282
+ # [:after_remove]
1283
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is removed</b> from the association collection.
1258
1284
  #
1259
1285
  # Option examples:
1260
1286
  # has_many :comments, -> { order("posted_on") }
@@ -1274,12 +1300,10 @@ module ActiveRecord
1274
1300
  Reflection.add_reflection self, name, reflection
1275
1301
  end
1276
1302
 
1277
- # Specifies a one-to-one association with another class. This method
1278
- # should only be used if the other class contains the foreign key. If
1279
- # the current class contains the foreign key, then you should use
1280
- # #belongs_to instead. See {Is it a belongs_to or has_one
1281
- # association?}[rdoc-ref:Associations::ClassMethods@Is+it+a+-23belongs_to+or+-23has_one+association-3F]
1282
- # for more detail on when to use #has_one and when to use #belongs_to.
1303
+ # Specifies a one-to-one association with another class. This method should only be used
1304
+ # if the other class contains the foreign key. If the current class contains the foreign key,
1305
+ # then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview
1306
+ # on when to use #has_one and when to use #belongs_to.
1283
1307
  #
1284
1308
  # The following methods for retrieval and query of a single associated object will be added:
1285
1309
  #
@@ -1394,10 +1418,8 @@ module ActiveRecord
1394
1418
  # If you are going to modify the association (rather than just read from it), then it is
1395
1419
  # a good idea to set the <tt>:inverse_of</tt> option on the source association on the
1396
1420
  # join model. This allows associated records to be built which will automatically create
1397
- # the appropriate join model records when they are saved. See
1398
- # {Association Join Models}[rdoc-ref:Associations::ClassMethods@Association+Join+Models]
1399
- # and {Setting Inverses}[rdoc-ref:Associations::ClassMethods@Setting+Inverses] for
1400
- # more detail.
1421
+ # the appropriate join model records when they are saved. (See the 'Association Join Models'
1422
+ # and 'Setting Inverses' sections above.)
1401
1423
  # [+:disable_joins+]
1402
1424
  # Specifies whether joins should be skipped for an association. If set to true, two or more queries
1403
1425
  # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory
@@ -1435,8 +1457,7 @@ module ActiveRecord
1435
1457
  # [+:inverse_of+]
1436
1458
  # Specifies the name of the #belongs_to association on the associated object
1437
1459
  # that is the inverse of this #has_one association.
1438
- # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations]
1439
- # for more detail.
1460
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
1440
1461
  # [+:required+]
1441
1462
  # When set to +true+, the association will also have its presence validated.
1442
1463
  # This will validate the association itself, not the id. You can use
@@ -1470,12 +1491,10 @@ module ActiveRecord
1470
1491
  Reflection.add_reflection self, name, reflection
1471
1492
  end
1472
1493
 
1473
- # Specifies a one-to-one association with another class. This method
1474
- # should only be used if this class contains the foreign key. If the
1475
- # other class contains the foreign key, then you should use #has_one
1476
- # instead. See {Is it a belongs_to or has_one
1477
- # association?}[rdoc-ref:Associations::ClassMethods@Is+it+a+-23belongs_to+or+-23has_one+association-3F]
1478
- # for more detail on when to use #has_one and when to use #belongs_to.
1494
+ # Specifies a one-to-one association with another class. This method should only be used
1495
+ # if this class contains the foreign key. If the other class contains the foreign key,
1496
+ # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview
1497
+ # on when to use #has_one and when to use #belongs_to.
1479
1498
  #
1480
1499
  # Methods will be added for retrieval and query for a single associated object, for which
1481
1500
  # this object holds an id:
@@ -1617,8 +1636,7 @@ module ActiveRecord
1617
1636
  # [+:inverse_of+]
1618
1637
  # Specifies the name of the #has_one or #has_many association on the associated
1619
1638
  # object that is the inverse of this #belongs_to association.
1620
- # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations]
1621
- # for more detail.
1639
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
1622
1640
  # [+:optional+]
1623
1641
  # When set to +true+, the association will not have its presence validated.
1624
1642
  # [+:required+]
@@ -1678,7 +1696,7 @@ module ActiveRecord
1678
1696
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1679
1697
  # join table with a migration such as this:
1680
1698
  #
1681
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[7.2]
1699
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0]
1682
1700
  # def change
1683
1701
  # create_join_table :developers, :projects
1684
1702
  # end
@@ -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
@@ -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
@@ -431,33 +465,34 @@ module ActiveRecord
431
465
  return unless association && association.loaded?
432
466
 
433
467
  record = association.load_target
468
+ return unless record && !record.destroyed?
434
469
 
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) }
470
+ autosave = reflection.options[:autosave]
443
471
 
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)
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)
448
478
 
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
479
+ unless reflection.through_reflection
480
+ foreign_key = Array(reflection.foreign_key)
481
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
455
482
 
456
- saved = record.save(validate: !autosave)
457
- raise ActiveRecord::Rollback if !saved && autosave
458
- 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
459
486
  end
487
+ association.set_inverse_instance(record)
460
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
461
496
  end
462
497
  end
463
498
 
@@ -482,7 +517,6 @@ module ActiveRecord
482
517
  return false unless reflection.inverse_of&.polymorphic?
483
518
 
484
519
  class_name = record._read_attribute(reflection.inverse_of.foreign_type)
485
-
486
520
  reflection.active_record != record.class.polymorphic_class_for(class_name)
487
521
  end
488
522
 
@@ -502,7 +536,15 @@ module ActiveRecord
502
536
  foreign_key.each { |key| self[key] = nil }
503
537
  record.destroy
504
538
  elsif autosave != false
505
- 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
506
548
 
507
549
  if association.updated?
508
550
  primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
@@ -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