activerecord 7.2.2.1 → 8.0.5
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 +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- 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/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- 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/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- 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 +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- 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 +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- 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 +9 -9
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- 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 +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- 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 +1 -18
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- 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/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +37 -28
- 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 +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +3 -3
- 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 +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +40 -43
- 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 +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- 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 +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- 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_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- 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 +9 -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 +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- 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
|
|
@@ -214,6 +214,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
|
|
|
214
214
|
|
|
215
215
|
* https://github.com/rails/rails/issues
|
|
216
216
|
|
|
217
|
-
Feature requests should be discussed on the
|
|
217
|
+
Feature requests should be discussed on the rubyonrails-core forum here:
|
|
218
218
|
|
|
219
219
|
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
|
@@ -26,16 +26,18 @@ module ActiveRecord
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def self.initial_count_for(connection, name, table_joins)
|
|
29
|
-
|
|
29
|
+
quoted_name_escaped = nil
|
|
30
|
+
name_escaped = nil
|
|
30
31
|
|
|
31
32
|
counts = table_joins.map do |join|
|
|
32
33
|
if join.is_a?(Arel::Nodes::StringJoin)
|
|
33
|
-
#
|
|
34
|
-
|
|
34
|
+
# quoted_name_escaped should be case ignored as some database adapters (Oracle) return quoted name in uppercase
|
|
35
|
+
quoted_name_escaped ||= Regexp.escape(connection.quote_table_name(name))
|
|
36
|
+
name_escaped ||= Regexp.escape(name)
|
|
35
37
|
|
|
36
38
|
# Table names + table aliases
|
|
37
39
|
join.left.scan(
|
|
38
|
-
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{
|
|
40
|
+
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name_escaped}|#{name_escaped})\sON/i
|
|
39
41
|
).size
|
|
40
42
|
elsif join.is_a?(Arel::Nodes::Join)
|
|
41
43
|
join.left.name == name ? 1 : 0
|
|
@@ -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
|
|
@@ -19,10 +19,16 @@ module ActiveRecord
|
|
|
19
19
|
id = owner.public_send(reflection.foreign_key)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
association_class = if reflection.polymorphic?
|
|
23
|
+
owner.public_send(reflection.foreign_type)
|
|
24
|
+
else
|
|
25
|
+
reflection.klass
|
|
26
|
+
end
|
|
27
|
+
|
|
22
28
|
enqueue_destroy_association(
|
|
23
29
|
owner_model_name: owner.class.to_s,
|
|
24
30
|
owner_id: owner.id,
|
|
25
|
-
association_class:
|
|
31
|
+
association_class: association_class.to_s,
|
|
26
32
|
association_ids: [id],
|
|
27
33
|
association_primary_key_column: primary_key_column,
|
|
28
34
|
ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
|
|
@@ -129,7 +135,9 @@ module ActiveRecord
|
|
|
129
135
|
target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
|
|
130
136
|
|
|
131
137
|
if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
|
|
138
|
+
owner_pk = Array(owner.class.primary_key)
|
|
132
139
|
reflection_fk.each_with_index do |key, index|
|
|
140
|
+
next if record.nil? && owner_pk.include?(key)
|
|
133
141
|
owner[key] = target_key_values[index]
|
|
134
142
|
end
|
|
135
143
|
end
|
|
@@ -156,7 +164,15 @@ module ActiveRecord
|
|
|
156
164
|
end
|
|
157
165
|
|
|
158
166
|
def stale_state
|
|
159
|
-
|
|
167
|
+
foreign_key = reflection.foreign_key
|
|
168
|
+
if foreign_key.is_a?(Array)
|
|
169
|
+
attributes = foreign_key.map do |fk|
|
|
170
|
+
owner._read_attribute(fk) { |n| owner.send(:missing_attribute, n, caller) }
|
|
171
|
+
end
|
|
172
|
+
attributes if attributes.any?
|
|
173
|
+
else
|
|
174
|
+
owner._read_attribute(foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
|
|
175
|
+
end
|
|
160
176
|
end
|
|
161
177
|
end
|
|
162
178
|
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 loaded?
|
|
263
|
+
target.include?(record)
|
|
264
|
+
elsif record.new_record?
|
|
265
|
+
include_in_memory?(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
|
|
@@ -38,41 +38,39 @@ module ActiveRecord
|
|
|
38
38
|
chain << [reflection, table]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
klass = reflection.klass
|
|
41
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
|
42
|
+
# more sense in this context), so we reverse
|
|
43
|
+
chain.reverse_each do |reflection, table|
|
|
44
|
+
klass = reflection.klass
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
unless scope.references_values.empty?
|
|
49
|
+
associations = scope.eager_load_values | scope.includes_values
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
end
|
|
51
|
+
unless associations.empty?
|
|
52
|
+
scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
|
|
55
53
|
end
|
|
54
|
+
end
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
arel = scope.arel(alias_tracker.aliases)
|
|
57
|
+
nodes = arel.constraints.first
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
59
|
+
if nodes.is_a?(Arel::Nodes::And)
|
|
60
|
+
others = nodes.children.extract! do |node|
|
|
61
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
|
64
62
|
end
|
|
63
|
+
end
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
joins << join_type.new(table, Arel::Nodes::On.new(nodes))
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# The current table in this iteration becomes the foreign table in the next
|
|
74
|
-
foreign_table, foreign_klass = table, klass
|
|
67
|
+
if others && !others.empty?
|
|
68
|
+
joins.concat arel.join_sources
|
|
69
|
+
append_constraints(joins.last, others)
|
|
75
70
|
end
|
|
71
|
+
|
|
72
|
+
# The current table in this iteration becomes the foreign table in the next
|
|
73
|
+
foreign_table, foreign_klass = table, klass
|
|
76
74
|
end
|
|
77
75
|
|
|
78
76
|
joins
|
|
@@ -91,10 +89,10 @@ module ActiveRecord
|
|
|
91
89
|
end
|
|
92
90
|
|
|
93
91
|
private
|
|
94
|
-
def append_constraints(
|
|
92
|
+
def append_constraints(join, constraints)
|
|
95
93
|
if join.is_a?(Arel::Nodes::StringJoin)
|
|
96
94
|
join_string = Arel::Nodes::And.new(constraints.unshift join.left)
|
|
97
|
-
join.left =
|
|
95
|
+
join.left = join_string
|
|
98
96
|
else
|
|
99
97
|
right = join.right
|
|
100
98
|
right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
|
|
@@ -103,7 +103,7 @@ module ActiveRecord
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def instantiate(result_set, strict_loading_value, &block)
|
|
106
|
-
primary_key = aliases.column_alias(join_root,
|
|
106
|
+
primary_key = Array(join_root.primary_key).map { |column| aliases.column_alias(join_root, column) }
|
|
107
107
|
|
|
108
108
|
seen = Hash.new { |i, parent|
|
|
109
109
|
i[parent] = Hash.new { |j, child_class|
|
|
@@ -141,7 +141,7 @@ module ActiveRecord
|
|
|
141
141
|
|
|
142
142
|
message_bus.instrument("instantiation.active_record", payload) do
|
|
143
143
|
result_set.each { |row_hash|
|
|
144
|
-
parent_key = primary_key ? row_hash
|
|
144
|
+
parent_key = primary_key.empty? ? row_hash : row_hash.values_at(*primary_key)
|
|
145
145
|
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
|
|
146
146
|
construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
|
|
147
147
|
}
|
|
@@ -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)
|
|
@@ -38,7 +38,13 @@ module ActiveRecord
|
|
|
38
38
|
attr_reader :loaders
|
|
39
39
|
|
|
40
40
|
def group_and_load_similar(loaders)
|
|
41
|
-
loaders.grep_v(ThroughAssociation)
|
|
41
|
+
non_through = loaders.grep_v(ThroughAssociation)
|
|
42
|
+
|
|
43
|
+
grouped = non_through.group_by do |loader|
|
|
44
|
+
[loader.loader_query, loader.klass]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
grouped.each do |(query, _klass), similar_loaders|
|
|
42
48
|
query.load_records_in_batch(similar_loaders)
|
|
43
49
|
end
|
|
44
50
|
end
|
|
@@ -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
|