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.
- 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
|