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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +173 -920
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -9
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +50 -32
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +0 -27
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +23 -45
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +41 -93
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +7 -32
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +15 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +1 -1
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_cache.rb +5 -4
- data/lib/active_record/query_logs.rb +98 -40
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +3 -4
- data/lib/active_record/reflection.rb +9 -7
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +25 -20
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +5 -0
- data/lib/active_record/relation/query_methods.rb +81 -75
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +24 -15
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/testing/query_assertions.rb +2 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/visitors/sqlite.rb +0 -25
- 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[
|
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 :
|
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
|
-
|
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
|
34
|
-
define_callbacks
|
35
|
-
define_validations
|
36
|
-
define_change_tracking_methods
|
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
|
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?
|
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.
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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(
|
382
|
+
# def congratulate_client(client)
|
383
383
|
# # ...
|
384
384
|
# end
|
385
385
|
#
|
386
|
-
# def log_after_remove(
|
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: [
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
1279
|
-
#
|
1280
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
1475
|
-
#
|
1476
|
-
#
|
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
|
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[
|
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
|
26
|
-
@
|
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
|
-
@
|
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
|
-
|
52
|
-
|
54
|
+
session = Session.new
|
55
|
+
@stack << session
|
53
56
|
end
|
54
57
|
|
55
|
-
def finalize_session
|
56
|
-
@
|
57
|
-
|
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 = :
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
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
|
-
|
450
|
-
|
451
|
-
|
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
|
-
|
457
|
-
|
458
|
-
|
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 =
|
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
|
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
|