activerecord 7.2.1.1 → 8.0.0.rc1
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 +220 -756
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/primary_key.rb +2 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -12
- data/lib/active_record/attributes.rb +1 -2
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +26 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -4
- 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 +24 -26
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -42
- 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/cidr.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 +0 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +54 -14
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -97
- 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 +28 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +54 -75
- 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 +4 -4
- 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/marshalling.rb +4 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -38
- data/lib/active_record/model_schema.rb +4 -6
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_cache.rb +0 -4
- 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 +9 -38
- data/lib/active_record/railties/databases.rake +1 -1
- data/lib/active_record/reflection.rb +23 -23
- 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 +41 -40
- 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 +14 -1
- data/lib/active_record/relation/query_methods.rb +122 -71
- 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 +40 -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/testing/query_assertions.rb +2 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -45
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/sqlite.rb +25 -0
- metadata +10 -11
- 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
|
@@ -172,7 +179,7 @@ module ActiveRecord
|
|
172
179
|
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
173
180
|
# not reraised. The proxy is \reset and +nil+ is the return value.
|
174
181
|
def load_target
|
175
|
-
@target = find_target if (@stale_state && stale_target?) || find_target?
|
182
|
+
@target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
|
176
183
|
|
177
184
|
loaded! unless loaded?
|
178
185
|
target
|
@@ -180,6 +187,13 @@ module ActiveRecord
|
|
180
187
|
reset
|
181
188
|
end
|
182
189
|
|
190
|
+
def async_load_target # :nodoc:
|
191
|
+
@target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
|
192
|
+
|
193
|
+
loaded! unless loaded?
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
183
197
|
# We can't dump @reflection and @through_reflection since it contains the scope proc
|
184
198
|
def marshal_dump
|
185
199
|
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
|
@@ -223,13 +237,19 @@ module ActiveRecord
|
|
223
237
|
klass
|
224
238
|
end
|
225
239
|
|
226
|
-
def find_target
|
240
|
+
def find_target(async: false)
|
227
241
|
if violates_strict_loading?
|
228
242
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
229
243
|
end
|
230
244
|
|
231
245
|
scope = self.scope
|
232
|
-
|
246
|
+
if skip_statement_cache?(scope)
|
247
|
+
if async
|
248
|
+
return scope.load_async.then(&:to_a)
|
249
|
+
else
|
250
|
+
return scope.to_a
|
251
|
+
end
|
252
|
+
end
|
233
253
|
|
234
254
|
sc = reflection.association_scope_cache(klass, owner) do |params|
|
235
255
|
as = AssociationScope.create { params.bind }
|
@@ -238,7 +258,7 @@ module ActiveRecord
|
|
238
258
|
|
239
259
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
240
260
|
klass.with_connection do |c|
|
241
|
-
sc.execute(binds, c) do |record|
|
261
|
+
sc.execute(binds, c, async: async) do |record|
|
242
262
|
set_inverse_instance(record)
|
243
263
|
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
244
264
|
record.strict_loading!
|
@@ -30,10 +30,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
30
30
|
end
|
31
31
|
|
32
32
|
reflection = create_reflection(model, name, scope, options, &block)
|
33
|
-
define_accessors
|
34
|
-
define_callbacks
|
35
|
-
define_validations
|
36
|
-
define_change_tracking_methods
|
33
|
+
define_accessors(model, reflection)
|
34
|
+
define_callbacks(model, reflection)
|
35
|
+
define_validations(model, reflection)
|
36
|
+
define_change_tracking_methods(model, reflection)
|
37
37
|
reflection
|
38
38
|
end
|
39
39
|
|
@@ -71,6 +71,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def self.define_extensions(model, name)
|
74
|
+
# noop
|
74
75
|
end
|
75
76
|
|
76
77
|
def self.define_callbacks(model, reflection)
|
@@ -81,7 +82,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
81
82
|
end
|
82
83
|
|
83
84
|
Association.extensions.each do |extension|
|
84
|
-
extension.build
|
85
|
+
extension.build(model, reflection)
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
@@ -131,7 +132,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
131
132
|
err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
|
132
133
|
raise ActiveRecord::ConfigurationError, err_message
|
133
134
|
end
|
134
|
-
unless valid_dependent_options.include?
|
135
|
+
unless valid_dependent_options.include?(dependent)
|
135
136
|
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
|
136
137
|
end
|
137
138
|
end
|
@@ -94,7 +94,7 @@ module ActiveRecord
|
|
94
94
|
def find(*args)
|
95
95
|
if options[:inverse_of] && loaded?
|
96
96
|
args_flatten = args.flatten
|
97
|
-
model = scope.
|
97
|
+
model = scope.model
|
98
98
|
|
99
99
|
if args_flatten.blank?
|
100
100
|
error_message = "Couldn't find #{model.name} without an ID"
|
@@ -256,14 +256,16 @@ module ActiveRecord
|
|
256
256
|
end
|
257
257
|
|
258
258
|
def include?(record)
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
259
|
+
klass = reflection.klass
|
260
|
+
return false unless record.is_a?(klass)
|
261
|
+
|
262
|
+
if record.new_record?
|
263
|
+
include_in_memory?(record)
|
264
|
+
elsif loaded?
|
265
|
+
target.include?(record)
|
265
266
|
else
|
266
|
-
|
267
|
+
record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
|
268
|
+
scope.exists?(record_id)
|
267
269
|
end
|
268
270
|
end
|
269
271
|
|
@@ -47,7 +47,7 @@ module ActiveRecord
|
|
47
47
|
end
|
48
48
|
|
49
49
|
if scope.order_values.empty? && ordered
|
50
|
-
split_scope = DisableJoinsAssociationRelation.create(scope.
|
50
|
+
split_scope = DisableJoinsAssociationRelation.create(scope.model, key, join_ids)
|
51
51
|
split_scope.where_clause += scope.where_clause
|
52
52
|
split_scope
|
53
53
|
else
|
@@ -93,7 +93,13 @@ module ActiveRecord
|
|
93
93
|
@through_scope = scope
|
94
94
|
record = super
|
95
95
|
|
96
|
-
inverse =
|
96
|
+
inverse =
|
97
|
+
if source_reflection.polymorphic?
|
98
|
+
source_reflection.polymorphic_inverse_of(record.class)
|
99
|
+
else
|
100
|
+
source_reflection.inverse_of
|
101
|
+
end
|
102
|
+
|
97
103
|
if inverse
|
98
104
|
if inverse.collection?
|
99
105
|
record.send(inverse.name) << build_through_record(record)
|
@@ -140,7 +146,7 @@ module ActiveRecord
|
|
140
146
|
|
141
147
|
case method
|
142
148
|
when :destroy
|
143
|
-
if scope.
|
149
|
+
if scope.model.primary_key
|
144
150
|
count = scope.destroy_all.count(&:destroyed?)
|
145
151
|
else
|
146
152
|
scope.each(&:_run_destroy_callbacks)
|
@@ -216,7 +222,8 @@ module ActiveRecord
|
|
216
222
|
end
|
217
223
|
end
|
218
224
|
|
219
|
-
def find_target
|
225
|
+
def find_target(async: false)
|
226
|
+
raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async
|
220
227
|
return [] unless target_reflection_has_associated_record?
|
221
228
|
return scope.to_a if disable_joins
|
222
229
|
super
|
@@ -25,8 +25,9 @@ module ActiveRecord
|
|
25
25
|
joins = []
|
26
26
|
chain = []
|
27
27
|
|
28
|
-
reflection.chain
|
29
|
-
|
28
|
+
reflection_chain = reflection.chain
|
29
|
+
reflection_chain.each_with_index do |reflection, index|
|
30
|
+
table, terminated = yield reflection, reflection_chain[index..]
|
30
31
|
@table ||= table
|
31
32
|
|
32
33
|
if terminated
|
@@ -190,12 +190,12 @@ module ActiveRecord
|
|
190
190
|
def make_constraints(parent, child, join_type)
|
191
191
|
foreign_table = parent.table
|
192
192
|
foreign_klass = parent.base_klass
|
193
|
-
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
|
194
|
-
table, terminated = @joined_tables[
|
193
|
+
child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain|
|
194
|
+
table, terminated = @joined_tables[remaining_reflection_chain]
|
195
195
|
root = reflection == child.reflection
|
196
196
|
|
197
197
|
if table && (!root || !terminated)
|
198
|
-
@joined_tables[
|
198
|
+
@joined_tables[remaining_reflection_chain] = [table, root] if root
|
199
199
|
next table, true
|
200
200
|
end
|
201
201
|
|
@@ -206,7 +206,7 @@ module ActiveRecord
|
|
206
206
|
root ? name : "#{name}_join"
|
207
207
|
end
|
208
208
|
|
209
|
-
@joined_tables[
|
209
|
+
@joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
|
210
210
|
table
|
211
211
|
end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
|
212
212
|
end
|
@@ -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
|
#
|
@@ -1251,6 +1273,14 @@ module ActiveRecord
|
|
1251
1273
|
# persisted new records placed at the end.
|
1252
1274
|
# When set to +:nested_attributes_order+, the index is based on the record order received by
|
1253
1275
|
# nested attributes setter, when accepts_nested_attributes_for is used.
|
1276
|
+
# [:before_add]
|
1277
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is added</b> to the association collection.
|
1278
|
+
# [:after_add]
|
1279
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is added</b> to the association collection.
|
1280
|
+
# [:before_remove]
|
1281
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is removed</b> from the association collection.
|
1282
|
+
# [:after_remove]
|
1283
|
+
# Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is removed</b> from the association collection.
|
1254
1284
|
#
|
1255
1285
|
# Option examples:
|
1256
1286
|
# has_many :comments, -> { order("posted_on") }
|
@@ -1666,7 +1696,7 @@ module ActiveRecord
|
|
1666
1696
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
1667
1697
|
# join table with a migration such as this:
|
1668
1698
|
#
|
1669
|
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[
|
1699
|
+
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0]
|
1670
1700
|
# def change
|
1671
1701
|
# create_join_table :developers, :projects
|
1672
1702
|
# end
|
@@ -1,29 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "concurrent/atomic/atomic_boolean"
|
4
|
+
require "concurrent/atomic/read_write_lock"
|
5
|
+
|
3
6
|
module ActiveRecord
|
4
7
|
class AsynchronousQueriesTracker # :nodoc:
|
5
|
-
module NullSession # :nodoc:
|
6
|
-
class << self
|
7
|
-
def active?
|
8
|
-
true
|
9
|
-
end
|
10
|
-
|
11
|
-
def finalize
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
class Session # :nodoc:
|
17
9
|
def initialize
|
18
|
-
@active = true
|
10
|
+
@active = Concurrent::AtomicBoolean.new(true)
|
11
|
+
@lock = Concurrent::ReadWriteLock.new
|
19
12
|
end
|
20
13
|
|
21
14
|
def active?
|
22
|
-
@active
|
15
|
+
@active.true?
|
23
16
|
end
|
24
17
|
|
25
|
-
def
|
26
|
-
@
|
18
|
+
def synchronize(&block)
|
19
|
+
@lock.with_read_lock(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def finalize(wait = false)
|
23
|
+
@active.make_false
|
24
|
+
if wait
|
25
|
+
# Wait until all thread with a read lock are done
|
26
|
+
@lock.with_write_lock { }
|
27
|
+
end
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -33,7 +34,7 @@ module ActiveRecord
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def run
|
36
|
-
ActiveRecord::Base.asynchronous_queries_tracker.start_session
|
37
|
+
ActiveRecord::Base.asynchronous_queries_tracker.tap(&:start_session)
|
37
38
|
end
|
38
39
|
|
39
40
|
def complete(asynchronous_queries_tracker)
|
@@ -41,20 +42,23 @@ module ActiveRecord
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
|
-
attr_reader :current_session
|
45
|
-
|
46
45
|
def initialize
|
47
|
-
@
|
46
|
+
@stack = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def current_session
|
50
|
+
@stack.last or raise ActiveRecordError, "Can't perform asynchronous queries without a query session"
|
48
51
|
end
|
49
52
|
|
50
53
|
def start_session
|
51
|
-
|
52
|
-
|
54
|
+
session = Session.new
|
55
|
+
@stack << session
|
53
56
|
end
|
54
57
|
|
55
|
-
def finalize_session
|
56
|
-
@
|
57
|
-
|
58
|
+
def finalize_session(wait = false)
|
59
|
+
session = @stack.pop
|
60
|
+
session&.finalize(wait)
|
61
|
+
self
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
@@ -4,21 +4,29 @@ module ActiveRecord
|
|
4
4
|
module AttributeAssignment
|
5
5
|
private
|
6
6
|
def _assign_attributes(attributes)
|
7
|
-
multi_parameter_attributes = nil
|
7
|
+
multi_parameter_attributes = nested_parameter_attributes = nil
|
8
8
|
|
9
9
|
attributes.each do |k, v|
|
10
10
|
key = k.to_s
|
11
11
|
|
12
12
|
if key.include?("(")
|
13
13
|
(multi_parameter_attributes ||= {})[key] = v
|
14
|
+
elsif v.is_a?(Hash)
|
15
|
+
(nested_parameter_attributes ||= {})[key] = v
|
14
16
|
else
|
15
17
|
_assign_attribute(key, v)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
21
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
|
19
22
|
assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
|
20
23
|
end
|
21
24
|
|
25
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
26
|
+
def assign_nested_parameter_attributes(pairs)
|
27
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
28
|
+
end
|
29
|
+
|
22
30
|
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
23
31
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
24
32
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
@@ -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,10 +28,14 @@ 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
|
|
35
|
+
def ==(other)
|
36
|
+
other.is_a?(self.class) && __getobj__ == other.__getobj__
|
37
|
+
end
|
38
|
+
|
35
39
|
private
|
36
40
|
def convert_time_to_time_zone(value)
|
37
41
|
return if value.nil?
|
@@ -41,23 +45,13 @@ module ActiveRecord
|
|
41
45
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
42
46
|
value
|
43
47
|
else
|
44
|
-
|
48
|
+
map(value) { |v| convert_time_to_time_zone(v) }
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def set_time_zone_without_conversion(value)
|
49
53
|
::Time.zone.local_to_utc(value).try(:in_time_zone) if value
|
50
54
|
end
|
51
|
-
|
52
|
-
def map_avoiding_infinite_recursion(value)
|
53
|
-
map(value) do |v|
|
54
|
-
if value.equal?(v)
|
55
|
-
nil
|
56
|
-
else
|
57
|
-
yield(v)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
55
|
end
|
62
56
|
|
63
57
|
extend ActiveSupport::Concern
|
@@ -212,8 +212,7 @@ module ActiveRecord
|
|
212
212
|
#--
|
213
213
|
# Implemented by ActiveModel::AttributeRegistration#attribute.
|
214
214
|
|
215
|
-
# This
|
216
|
-
# accepts type objects, and will do its work immediately instead of
|
215
|
+
# This API only accepts type objects, and will do its work immediately instead of
|
217
216
|
# waiting for the schema to load. While this method
|
218
217
|
# is provided so it can be used by plugin authors, application code
|
219
218
|
# should probably use ClassMethods#attribute.
|