activerecord 7.2.2 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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