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