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