activerecord 7.2.2 → 8.0.0
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +239 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- 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 +3 -2
- 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 +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +2 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/autosave_association.rb +69 -27
- 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 -9
- 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_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +33 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -39
- 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/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -95
- 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 +53 -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_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +18 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +8 -0
- 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/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +27 -10
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -38
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +2 -17
- data/lib/active_record/reflection.rb +18 -21
- 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 +40 -39
- 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 +13 -0
- data/lib/active_record/relation/query_methods.rb +115 -65
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +79 -61
- data/lib/active_record/result.rb +66 -4
- 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/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +48 -47
- 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/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +15 -45
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +11 -12
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
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
|
|
@@ -113,6 +120,14 @@ module ActiveRecord
|
|
|
113
120
|
@association_scope = nil
|
|
114
121
|
end
|
|
115
122
|
|
|
123
|
+
def set_strict_loading(record)
|
|
124
|
+
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
|
125
|
+
record.strict_loading!
|
|
126
|
+
else
|
|
127
|
+
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
116
131
|
# Set the inverse association, if possible
|
|
117
132
|
def set_inverse_instance(record)
|
|
118
133
|
if inverse = inverse_association_for(record)
|
|
@@ -172,7 +187,7 @@ module ActiveRecord
|
|
|
172
187
|
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
|
173
188
|
# not reraised. The proxy is \reset and +nil+ is the return value.
|
|
174
189
|
def load_target
|
|
175
|
-
@target = find_target if (@stale_state && stale_target?) || find_target?
|
|
190
|
+
@target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
|
|
176
191
|
|
|
177
192
|
loaded! unless loaded?
|
|
178
193
|
target
|
|
@@ -180,6 +195,13 @@ module ActiveRecord
|
|
|
180
195
|
reset
|
|
181
196
|
end
|
|
182
197
|
|
|
198
|
+
def async_load_target # :nodoc:
|
|
199
|
+
@target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
|
|
200
|
+
|
|
201
|
+
loaded! unless loaded?
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
183
205
|
# We can't dump @reflection and @through_reflection since it contains the scope proc
|
|
184
206
|
def marshal_dump
|
|
185
207
|
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
|
|
@@ -223,13 +245,19 @@ module ActiveRecord
|
|
|
223
245
|
klass
|
|
224
246
|
end
|
|
225
247
|
|
|
226
|
-
def find_target
|
|
248
|
+
def find_target(async: false)
|
|
227
249
|
if violates_strict_loading?
|
|
228
250
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
|
229
251
|
end
|
|
230
252
|
|
|
231
253
|
scope = self.scope
|
|
232
|
-
|
|
254
|
+
if skip_statement_cache?(scope)
|
|
255
|
+
if async
|
|
256
|
+
return scope.load_async.then(&:to_a)
|
|
257
|
+
else
|
|
258
|
+
return scope.to_a
|
|
259
|
+
end
|
|
260
|
+
end
|
|
233
261
|
|
|
234
262
|
sc = reflection.association_scope_cache(klass, owner) do |params|
|
|
235
263
|
as = AssociationScope.create { params.bind }
|
|
@@ -238,13 +266,9 @@ module ActiveRecord
|
|
|
238
266
|
|
|
239
267
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
|
240
268
|
klass.with_connection do |c|
|
|
241
|
-
sc.execute(binds, c) do |record|
|
|
269
|
+
sc.execute(binds, c, async: async) do |record|
|
|
242
270
|
set_inverse_instance(record)
|
|
243
|
-
|
|
244
|
-
record.strict_loading!
|
|
245
|
-
else
|
|
246
|
-
record.strict_loading!(false, mode: owner.strict_loading_mode)
|
|
247
|
-
end
|
|
271
|
+
set_strict_loading(record)
|
|
248
272
|
end
|
|
249
273
|
end
|
|
250
274
|
end
|
|
@@ -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
|
|
@@ -146,7 +146,7 @@ module ActiveRecord
|
|
|
146
146
|
|
|
147
147
|
case method
|
|
148
148
|
when :destroy
|
|
149
|
-
if scope.
|
|
149
|
+
if scope.model.primary_key
|
|
150
150
|
count = scope.destroy_all.count(&:destroyed?)
|
|
151
151
|
else
|
|
152
152
|
scope.each(&:_run_destroy_callbacks)
|
|
@@ -222,7 +222,8 @@ module ActiveRecord
|
|
|
222
222
|
end
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
-
def find_target
|
|
225
|
+
def find_target(async: false)
|
|
226
|
+
raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async
|
|
226
227
|
return [] unless target_reflection_has_associated_record?
|
|
227
228
|
return scope.to_a if disable_joins
|
|
228
229
|
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
|
#
|
|
@@ -1255,6 +1277,14 @@ module ActiveRecord
|
|
|
1255
1277
|
# persisted new records placed at the end.
|
|
1256
1278
|
# When set to +:nested_attributes_order+, the index is based on the record order received by
|
|
1257
1279
|
# nested attributes setter, when accepts_nested_attributes_for is used.
|
|
1280
|
+
# [:before_add]
|
|
1281
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is added</b> to the association collection.
|
|
1282
|
+
# [:after_add]
|
|
1283
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is added</b> to the association collection.
|
|
1284
|
+
# [:before_remove]
|
|
1285
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is removed</b> from the association collection.
|
|
1286
|
+
# [:after_remove]
|
|
1287
|
+
# 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
1288
|
#
|
|
1259
1289
|
# Option examples:
|
|
1260
1290
|
# has_many :comments, -> { order("posted_on") }
|
|
@@ -1678,7 +1708,7 @@ module ActiveRecord
|
|
|
1678
1708
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
|
1679
1709
|
# join table with a migration such as this:
|
|
1680
1710
|
#
|
|
1681
|
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[
|
|
1711
|
+
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0]
|
|
1682
1712
|
# def change
|
|
1683
1713
|
# create_join_table :developers, :projects
|
|
1684
1714
|
# 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
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
module ActiveRecord
|
|
6
4
|
module AttributeMethods
|
|
7
5
|
# = Active Record Attribute Methods Primary Key
|
|
@@ -89,10 +87,9 @@ module ActiveRecord
|
|
|
89
87
|
@composite_primary_key
|
|
90
88
|
end
|
|
91
89
|
|
|
92
|
-
# Returns a quoted version of the primary key name
|
|
93
|
-
# SQL statements.
|
|
90
|
+
# Returns a quoted version of the primary key name.
|
|
94
91
|
def quoted_primary_key
|
|
95
|
-
|
|
92
|
+
adapter_class.quote_column_name(primary_key)
|
|
96
93
|
end
|
|
97
94
|
|
|
98
95
|
def reset_primary_key # :nodoc:
|
|
@@ -138,7 +135,6 @@ module ActiveRecord
|
|
|
138
135
|
elsif value
|
|
139
136
|
-value.to_s
|
|
140
137
|
end
|
|
141
|
-
@quoted_primary_key = nil
|
|
142
138
|
@attributes_builder = nil
|
|
143
139
|
end
|
|
144
140
|
|
|
@@ -148,7 +144,6 @@ module ActiveRecord
|
|
|
148
144
|
base.class_eval do
|
|
149
145
|
@primary_key = PRIMARY_KEY_NOT_SET
|
|
150
146
|
@composite_primary_key = false
|
|
151
|
-
@quoted_primary_key = nil
|
|
152
147
|
@attributes_builder = nil
|
|
153
148
|
end
|
|
154
149
|
end
|
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
|
28
28
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
29
29
|
value
|
|
30
30
|
else
|
|
31
|
-
|
|
31
|
+
map(super) { |v| cast(v) }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -45,23 +45,13 @@ module ActiveRecord
|
|
|
45
45
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
46
46
|
value
|
|
47
47
|
else
|
|
48
|
-
|
|
48
|
+
map(value) { |v| convert_time_to_time_zone(v) }
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def set_time_zone_without_conversion(value)
|
|
53
53
|
::Time.zone.local_to_utc(value).try(:in_time_zone) if value
|
|
54
54
|
end
|
|
55
|
-
|
|
56
|
-
def map_avoiding_infinite_recursion(value)
|
|
57
|
-
map(value) do |v|
|
|
58
|
-
if value.equal?(v)
|
|
59
|
-
nil
|
|
60
|
-
else
|
|
61
|
-
yield(v)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
55
|
end
|
|
66
56
|
|
|
67
57
|
extend ActiveSupport::Concern
|
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
|
84
84
|
attribute_method_patterns_cache.clear
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
87
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
|
88
88
|
old_name = old_name.to_s
|
|
89
89
|
|
|
90
90
|
if !abstract_class? && !has_attribute?(old_name)
|
|
@@ -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)
|