activerecord 7.2.3 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -958
- 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 +1 -1
- 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 +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- 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 +6 -15
- 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 +34 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- 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 +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- 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 +46 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -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 +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- 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 +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- 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 +16 -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 +13 -9
- 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 +31 -11
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- 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 +11 -35
- 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 +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- 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 +14 -14
- 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 +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +9 -10
- 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
|
|
@@ -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:
|
|
@@ -132,13 +129,13 @@ module ActiveRecord
|
|
|
132
129
|
# Project.primary_key # => "foo_id"
|
|
133
130
|
def primary_key=(value)
|
|
134
131
|
@primary_key = if value.is_a?(Array)
|
|
135
|
-
@composite_primary_key = true
|
|
136
132
|
include CompositePrimaryKey
|
|
137
133
|
@primary_key = value.map { |v| -v.to_s }.freeze
|
|
138
134
|
elsif value
|
|
139
135
|
-value.to_s
|
|
140
136
|
end
|
|
141
|
-
|
|
137
|
+
|
|
138
|
+
@composite_primary_key = value.is_a?(Array)
|
|
142
139
|
@attributes_builder = nil
|
|
143
140
|
end
|
|
144
141
|
|
|
@@ -148,7 +145,6 @@ module ActiveRecord
|
|
|
148
145
|
base.class_eval do
|
|
149
146
|
@primary_key = PRIMARY_KEY_NOT_SET
|
|
150
147
|
@composite_primary_key = false
|
|
151
|
-
@quoted_primary_key = nil
|
|
152
148
|
@attributes_builder = nil
|
|
153
149
|
end
|
|
154
150
|
end
|
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module AttributeMethods
|
|
5
5
|
# = Active Record Attribute Methods \Query
|
|
6
|
+
#
|
|
7
|
+
# Adds query methods for attributes that return either +true+ or +false+
|
|
8
|
+
# depending on the attribute type and value.
|
|
9
|
+
#
|
|
10
|
+
# For Boolean attributes this will return +true+ if the value is present
|
|
11
|
+
# and return +false+ otherwise:
|
|
12
|
+
#
|
|
13
|
+
# class Product < ActiveRecord::Base
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# product = Product.new(archived: false)
|
|
17
|
+
# product.archived? # => false
|
|
18
|
+
# product.archived = true
|
|
19
|
+
# product.archived? # => true
|
|
20
|
+
#
|
|
21
|
+
# For Numeric attributes this will return +true+ if the value is a non-zero
|
|
22
|
+
# number and return +false+ otherwise:
|
|
23
|
+
#
|
|
24
|
+
# product.inventory_count = 0
|
|
25
|
+
# product.inventory_count? # => false
|
|
26
|
+
# product.inventory_count = 1
|
|
27
|
+
# product.inventory_count? # => true
|
|
28
|
+
#
|
|
29
|
+
# For other attributes it will return +true+ if the value is present
|
|
30
|
+
# and return +false+ otherwise:
|
|
31
|
+
#
|
|
32
|
+
# product.name = nil
|
|
33
|
+
# product.name? # => false
|
|
34
|
+
# product.name = " "
|
|
35
|
+
# product.name? # => false
|
|
36
|
+
# product.name = "Orange"
|
|
37
|
+
# product.name? # => true
|
|
6
38
|
module Query
|
|
7
39
|
extend ActiveSupport::Concern
|
|
8
40
|
|
|
@@ -10,6 +42,8 @@ module ActiveRecord
|
|
|
10
42
|
attribute_method_suffix "?", parameters: false
|
|
11
43
|
end
|
|
12
44
|
|
|
45
|
+
# Returns +true+ or +false+ for the attribute identified by +attr_name+,
|
|
46
|
+
# depending on the attribute type and value.
|
|
13
47
|
def query_attribute(attr_name)
|
|
14
48
|
value = self.public_send(attr_name)
|
|
15
49
|
|
|
@@ -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
|
|
@@ -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
|
|
@@ -441,33 +475,34 @@ module ActiveRecord
|
|
|
441
475
|
return unless association && association.loaded?
|
|
442
476
|
|
|
443
477
|
record = association.load_target
|
|
478
|
+
return unless record && !record.destroyed?
|
|
444
479
|
|
|
445
|
-
|
|
446
|
-
autosave = reflection.options[:autosave]
|
|
447
|
-
|
|
448
|
-
if autosave && record.marked_for_destruction?
|
|
449
|
-
record.destroy
|
|
450
|
-
elsif autosave != false
|
|
451
|
-
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
452
|
-
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
480
|
+
autosave = reflection.options[:autosave]
|
|
453
481
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
482
|
+
if autosave && record.marked_for_destruction?
|
|
483
|
+
record.destroy
|
|
484
|
+
elsif autosave != false
|
|
485
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
486
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
487
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
458
488
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
association.set_inverse_instance(record)
|
|
464
|
-
end
|
|
489
|
+
unless reflection.through_reflection
|
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
465
492
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
494
|
+
association_id = _read_attribute(primary_key)
|
|
495
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
469
496
|
end
|
|
497
|
+
association.set_inverse_instance(record)
|
|
470
498
|
end
|
|
499
|
+
|
|
500
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
501
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
|
502
|
+
|
|
503
|
+
saved = record.save(validate: !autosave)
|
|
504
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
|
505
|
+
saved
|
|
471
506
|
end
|
|
472
507
|
end
|
|
473
508
|
|
|
@@ -492,7 +527,6 @@ module ActiveRecord
|
|
|
492
527
|
return false unless reflection.inverse_of&.polymorphic?
|
|
493
528
|
|
|
494
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
-
|
|
496
530
|
reflection.active_record.polymorphic_name != class_name
|
|
497
531
|
end
|
|
498
532
|
|
|
@@ -512,7 +546,15 @@ module ActiveRecord
|
|
|
512
546
|
foreign_key.each { |key| self[key] = nil }
|
|
513
547
|
record.destroy
|
|
514
548
|
elsif autosave != false
|
|
515
|
-
saved =
|
|
549
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
550
|
+
begin
|
|
551
|
+
@autosaving_belongs_to_for ||= {}
|
|
552
|
+
@autosaving_belongs_to_for[association] = true
|
|
553
|
+
record.save(validate: !autosave)
|
|
554
|
+
ensure
|
|
555
|
+
@autosaving_belongs_to_for[association] = false
|
|
556
|
+
end
|
|
557
|
+
end
|
|
516
558
|
|
|
517
559
|
if association.updated?
|
|
518
560
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|