activerecord 7.1.1 → 7.1.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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +496 -0
- data/README.rdoc +1 -0
- data/lib/active_record/associations/association.rb +2 -1
- data/lib/active_record/associations/belongs_to_association.rb +4 -4
- data/lib/active_record/associations/collection_association.rb +4 -4
- data/lib/active_record/associations/has_many_association.rb +2 -2
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -12
- data/lib/active_record/associations/preloader/association.rb +4 -1
- data/lib/active_record/associations.rb +21 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +15 -11
- data/lib/active_record/attribute_methods/read.rb +3 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +47 -7
- data/lib/active_record/autosave_association.rb +5 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +19 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -19
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +37 -13
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -33
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +25 -21
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +49 -10
- data/lib/active_record/counter_cache.rb +7 -3
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/hash_config.rb +6 -2
- data/lib/active_record/delegated_type.rb +7 -7
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +6 -2
- data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +6 -9
- data/lib/active_record/errors.rb +5 -4
- data/lib/active_record/fixtures.rb +16 -0
- data/lib/active_record/future_result.rb +10 -0
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/internal_metadata.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +14 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +9 -5
- data/lib/active_record/model_schema.rb +12 -7
- data/lib/active_record/nested_attributes.rb +16 -5
- data/lib/active_record/normalization.rb +8 -0
- data/lib/active_record/persistence.rb +6 -5
- data/lib/active_record/promise.rb +1 -1
- data/lib/active_record/query_cache.rb +1 -1
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/railtie.rb +14 -14
- data/lib/active_record/railties/controller_runtime.rb +2 -1
- data/lib/active_record/railties/databases.rake +7 -7
- data/lib/active_record/reflection.rb +21 -3
- data/lib/active_record/relation/calculations.rb +28 -1
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/query_methods.rb +21 -7
- data/lib/active_record/relation.rb +30 -5
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/runtime_registry.rb +15 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/secure_token.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +35 -13
- data/lib/active_record/test_fixtures.rb +1 -0
- data/lib/active_record/timestamp.rb +3 -1
- data/lib/active_record.rb +2 -2
- data/lib/arel/nodes/homogeneous_in.rb +1 -1
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/to_sql.rb +2 -1
- metadata +14 -13
|
@@ -307,7 +307,7 @@ module ActiveRecord
|
|
|
307
307
|
# [:allow_destroy]
|
|
308
308
|
# If true, destroys any members from the attributes hash with a
|
|
309
309
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
|
310
|
-
# (e.g. 1, '1', true, or 'true'). This option is
|
|
310
|
+
# (e.g. 1, '1', true, or 'true'). This option is false by default.
|
|
311
311
|
# [:reject_if]
|
|
312
312
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
|
313
313
|
# that checks whether a record should be built for a certain attribute
|
|
@@ -332,11 +332,11 @@ module ActiveRecord
|
|
|
332
332
|
# nested attributes are going to be used when an associated record already
|
|
333
333
|
# exists. In general, an existing record may either be updated with the
|
|
334
334
|
# new set of attribute values or be replaced by a wholly new record
|
|
335
|
-
# containing those values. By default the +:update_only+ option is
|
|
335
|
+
# containing those values. By default the +:update_only+ option is false
|
|
336
336
|
# and the nested attributes are used to update the existing record only
|
|
337
337
|
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
|
338
338
|
# record will be instantiated and used to replace the existing one.
|
|
339
|
-
# However if the +:update_only+ option is
|
|
339
|
+
# However if the +:update_only+ option is true, the nested attributes
|
|
340
340
|
# are used to update the record's attributes always, regardless of
|
|
341
341
|
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
|
342
342
|
# associations.
|
|
@@ -519,12 +519,12 @@ module ActiveRecord
|
|
|
519
519
|
unless reject_new_record?(association_name, attributes)
|
|
520
520
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
|
521
521
|
end
|
|
522
|
-
elsif existing_record = existing_records
|
|
522
|
+
elsif existing_record = find_record_by_id(existing_records, attributes["id"])
|
|
523
523
|
unless call_reject_if(association_name, attributes)
|
|
524
524
|
# Make sure we are operating on the actual object which is in the association's
|
|
525
525
|
# proxy_target array (either by finding it, or adding it if not found)
|
|
526
526
|
# Take into account that the proxy_target may have changed due to callbacks
|
|
527
|
-
target_record = association.target
|
|
527
|
+
target_record = find_record_by_id(association.target, attributes["id"])
|
|
528
528
|
if target_record
|
|
529
529
|
existing_record = target_record
|
|
530
530
|
else
|
|
@@ -612,5 +612,16 @@ module ActiveRecord
|
|
|
612
612
|
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
|
613
613
|
model, "id", record_id)
|
|
614
614
|
end
|
|
615
|
+
|
|
616
|
+
def find_record_by_id(records, id)
|
|
617
|
+
return if records.empty?
|
|
618
|
+
|
|
619
|
+
if records.first.class.composite_primary_key?
|
|
620
|
+
id = Array(id).map(&:to_s)
|
|
621
|
+
records.find { |record| Array(record.id).map(&:to_s) == id }
|
|
622
|
+
else
|
|
623
|
+
records.find { |record| record.id.to_s == id.to_s }
|
|
624
|
+
end
|
|
625
|
+
end
|
|
615
626
|
end
|
|
616
627
|
end
|
|
@@ -49,6 +49,14 @@ module ActiveRecord # :nodoc:
|
|
|
49
49
|
# By default, the normalization will not be applied to +nil+ values. This
|
|
50
50
|
# behavior can be changed with the +:apply_to_nil+ option.
|
|
51
51
|
#
|
|
52
|
+
# Be aware that if your app was created before Rails 7.1, and your app
|
|
53
|
+
# marshals instances of the targeted model (for example, when caching),
|
|
54
|
+
# then you should set ActiveRecord.marshalling_format_version to +7.1+ or
|
|
55
|
+
# higher via either <tt>config.load_defaults 7.1</tt> or
|
|
56
|
+
# <tt>config.active_record.marshalling_format_version = 7.1</tt>.
|
|
57
|
+
# Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
|
|
58
|
+
# and raise +TypeError+.
|
|
59
|
+
#
|
|
52
60
|
# ==== Options
|
|
53
61
|
#
|
|
54
62
|
# * +:with+ - Any callable object that accepts the attribute's value as
|
|
@@ -115,7 +115,7 @@ module ActiveRecord
|
|
|
115
115
|
# ==== Options
|
|
116
116
|
#
|
|
117
117
|
# [:returning]
|
|
118
|
-
# (PostgreSQL only) An array of attributes to return for all successfully
|
|
118
|
+
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
|
119
119
|
# inserted records, which by default is the primary key.
|
|
120
120
|
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
121
121
|
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
@@ -205,7 +205,7 @@ module ActiveRecord
|
|
|
205
205
|
# ==== Options
|
|
206
206
|
#
|
|
207
207
|
# [:returning]
|
|
208
|
-
# (PostgreSQL only) An array of attributes to return for all successfully
|
|
208
|
+
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
|
209
209
|
# inserted records, which by default is the primary key.
|
|
210
210
|
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
211
211
|
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
@@ -271,7 +271,7 @@ module ActiveRecord
|
|
|
271
271
|
# ==== Options
|
|
272
272
|
#
|
|
273
273
|
# [:returning]
|
|
274
|
-
# (PostgreSQL only) An array of attributes to return for all successfully
|
|
274
|
+
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
|
275
275
|
# inserted records, which by default is the primary key.
|
|
276
276
|
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
277
277
|
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
@@ -456,7 +456,7 @@ module ActiveRecord
|
|
|
456
456
|
end
|
|
457
457
|
|
|
458
458
|
# Accepts a list of attribute names to be used in the WHERE clause
|
|
459
|
-
# of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for
|
|
459
|
+
# of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for +#first+ and +#last+ finder methods.
|
|
460
460
|
#
|
|
461
461
|
# class Developer < ActiveRecord::Base
|
|
462
462
|
# query_constraints :company_id, :id
|
|
@@ -469,7 +469,7 @@ module ActiveRecord
|
|
|
469
469
|
# developer.update!(name: "Nikita")
|
|
470
470
|
# # UPDATE "developers" SET "name" = 'Nikita' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
|
|
471
471
|
#
|
|
472
|
-
# It is possible to update attribute used in the
|
|
472
|
+
# # It is possible to update an attribute used in the query_constraints clause:
|
|
473
473
|
# developer.update!(company_id: 2)
|
|
474
474
|
# # UPDATE "developers" SET "company_id" = 2 WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
|
|
475
475
|
#
|
|
@@ -1076,6 +1076,7 @@ module ActiveRecord
|
|
|
1076
1076
|
end
|
|
1077
1077
|
|
|
1078
1078
|
@association_cache = fresh_object.instance_variable_get(:@association_cache)
|
|
1079
|
+
@association_cache.each_value { |association| association.owner = self }
|
|
1079
1080
|
@attributes = fresh_object.instance_variable_get(:@attributes)
|
|
1080
1081
|
@new_record = false
|
|
1081
1082
|
@previously_new_record = false
|
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
|
31
31
|
# Returns a new +ActiveRecord::Promise+ that will apply the passed block
|
|
32
32
|
# when the value is accessed:
|
|
33
33
|
#
|
|
34
|
-
# Post.
|
|
34
|
+
# Post.async_pick(:title).then { |title| title.upcase }.value
|
|
35
35
|
# # => "POST TITLE"
|
|
36
36
|
def then(&block)
|
|
37
37
|
Promise.new(@future_result, @block ? @block >> block : block)
|
|
@@ -30,7 +30,7 @@ module ActiveRecord
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def self.complete(pools)
|
|
33
|
-
pools.each { |pool| pool.disable_query_cache! }
|
|
33
|
+
pools.each { |pool| pool.disable_query_cache! unless pool.discarded? }
|
|
34
34
|
|
|
35
35
|
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
|
36
36
|
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
|
@@ -146,6 +146,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
146
146
|
config.after_initialize do |app|
|
|
147
147
|
ActiveSupport.on_load(:active_record) do
|
|
148
148
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
|
|
149
|
+
next if db_config.nil?
|
|
149
150
|
|
|
150
151
|
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
|
|
151
152
|
db_config.name,
|
|
@@ -261,8 +262,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
261
262
|
end
|
|
262
263
|
|
|
263
264
|
ActiveSupport.on_load(:active_record) do
|
|
264
|
-
|
|
265
|
-
configs = configs.except(
|
|
265
|
+
configs_used_in_other_initializers = configs.except(
|
|
266
266
|
:migration_error,
|
|
267
267
|
:database_selector,
|
|
268
268
|
:database_resolver,
|
|
@@ -279,7 +279,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
279
279
|
:use_schema_cache_dump
|
|
280
280
|
)
|
|
281
281
|
|
|
282
|
-
|
|
282
|
+
configs_used_in_other_initializers.each do |k, v|
|
|
283
283
|
next if k == :encryption
|
|
284
284
|
setter = "#{k}="
|
|
285
285
|
# Some existing initializers might rely on Active Record configuration
|
|
@@ -377,23 +377,23 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
|
377
377
|
end
|
|
378
378
|
|
|
379
379
|
initializer "active_record_encryption.configuration" do |app|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
config.after_initialize do |app|
|
|
383
|
-
ActiveRecord::Encryption.configure \
|
|
380
|
+
ActiveSupport.on_load(:active_record_encryption) do
|
|
381
|
+
ActiveRecord::Encryption.configure(
|
|
384
382
|
primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
|
|
385
383
|
deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
|
|
386
384
|
key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
|
|
387
|
-
**config.active_record.encryption
|
|
385
|
+
**app.config.active_record.encryption
|
|
386
|
+
)
|
|
388
387
|
|
|
388
|
+
auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app)
|
|
389
389
|
auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters
|
|
390
|
+
end
|
|
390
391
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
end
|
|
392
|
+
ActiveSupport.on_load(:active_record) do
|
|
393
|
+
# Support extended queries for deterministic attributes and validations
|
|
394
|
+
if ActiveRecord::Encryption.config.extend_queries
|
|
395
|
+
ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
|
|
396
|
+
ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
|
|
397
397
|
end
|
|
398
398
|
end
|
|
399
399
|
|
|
@@ -37,9 +37,10 @@ module ActiveRecord
|
|
|
37
37
|
db_rt_before_render = ActiveRecord::RuntimeRegistry.reset
|
|
38
38
|
self.db_runtime = (db_runtime || 0) + db_rt_before_render
|
|
39
39
|
runtime = super
|
|
40
|
+
queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
|
|
40
41
|
db_rt_after_render = ActiveRecord::RuntimeRegistry.reset
|
|
41
42
|
self.db_runtime += db_rt_after_render
|
|
42
|
-
runtime -
|
|
43
|
+
runtime - queries_rt
|
|
43
44
|
else
|
|
44
45
|
super
|
|
45
46
|
end
|
|
@@ -89,10 +89,10 @@ db_namespace = namespace :db do
|
|
|
89
89
|
task migrate: :load_config do
|
|
90
90
|
db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
|
|
91
91
|
|
|
92
|
-
if db_configs.size == 1
|
|
92
|
+
if db_configs.size == 1 && db_configs.first.primary?
|
|
93
93
|
ActiveRecord::Tasks::DatabaseTasks.migrate
|
|
94
94
|
else
|
|
95
|
-
mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
|
|
95
|
+
mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
|
|
96
96
|
|
|
97
97
|
mapped_versions.sort.each do |version, db_configs|
|
|
98
98
|
db_configs.each do |db_config|
|
|
@@ -195,7 +195,7 @@ db_namespace = namespace :db do
|
|
|
195
195
|
|
|
196
196
|
namespace :up do
|
|
197
197
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
198
|
-
desc
|
|
198
|
+
desc "Run the \"up\" on #{name} database for a given migration VERSION."
|
|
199
199
|
task name => :load_config do
|
|
200
200
|
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
|
201
201
|
|
|
@@ -204,7 +204,7 @@ db_namespace = namespace :db do
|
|
|
204
204
|
conn.migration_context.run(:up, ActiveRecord::Tasks::DatabaseTasks.target_version)
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
-
db_namespace["_dump"].invoke
|
|
207
|
+
db_namespace["_dump:#{name}"].invoke
|
|
208
208
|
end
|
|
209
209
|
end
|
|
210
210
|
end
|
|
@@ -226,7 +226,7 @@ db_namespace = namespace :db do
|
|
|
226
226
|
|
|
227
227
|
namespace :down do
|
|
228
228
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
229
|
-
desc
|
|
229
|
+
desc "Run the \"down\" on #{name} database for a given migration VERSION."
|
|
230
230
|
task name => :load_config do
|
|
231
231
|
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
|
|
232
232
|
|
|
@@ -235,7 +235,7 @@ db_namespace = namespace :db do
|
|
|
235
235
|
conn.migration_context.run(:down, ActiveRecord::Tasks::DatabaseTasks.target_version)
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
-
db_namespace["_dump"].invoke
|
|
238
|
+
db_namespace["_dump:#{name}"].invoke
|
|
239
239
|
end
|
|
240
240
|
end
|
|
241
241
|
end
|
|
@@ -269,7 +269,7 @@ db_namespace = namespace :db do
|
|
|
269
269
|
conn.migration_context.rollback(step)
|
|
270
270
|
end
|
|
271
271
|
|
|
272
|
-
db_namespace["_dump"].invoke
|
|
272
|
+
db_namespace["_dump:#{name}"].invoke
|
|
273
273
|
end
|
|
274
274
|
end
|
|
275
275
|
end
|
|
@@ -382,6 +382,7 @@ module ActiveRecord
|
|
|
382
382
|
@klass = options[:anonymous_class]
|
|
383
383
|
@plural_name = active_record.pluralize_table_names ?
|
|
384
384
|
name.to_s.pluralize : name.to_s
|
|
385
|
+
validate_reflection!
|
|
385
386
|
end
|
|
386
387
|
|
|
387
388
|
def autosave=(autosave)
|
|
@@ -433,6 +434,17 @@ module ActiveRecord
|
|
|
433
434
|
def derive_class_name
|
|
434
435
|
name.to_s.camelize
|
|
435
436
|
end
|
|
437
|
+
|
|
438
|
+
def validate_reflection!
|
|
439
|
+
return unless options[:foreign_key].is_a?(Array)
|
|
440
|
+
|
|
441
|
+
message = <<~MSG.squish
|
|
442
|
+
Passing #{options[:foreign_key]} array to :foreign_key option
|
|
443
|
+
on the #{active_record}##{name} association is not supported.
|
|
444
|
+
Use the query_constraints: #{options[:foreign_key]} option instead to represent a composite foreign key.
|
|
445
|
+
MSG
|
|
446
|
+
raise ArgumentError, message
|
|
447
|
+
end
|
|
436
448
|
end
|
|
437
449
|
|
|
438
450
|
# Holds all the metadata about an aggregation as it was specified in the
|
|
@@ -774,7 +786,7 @@ module ActiveRecord
|
|
|
774
786
|
primary_query_constraints = active_record.query_constraints_list
|
|
775
787
|
owner_pk = active_record.primary_key
|
|
776
788
|
|
|
777
|
-
if primary_query_constraints.size
|
|
789
|
+
if primary_query_constraints.size > 2
|
|
778
790
|
raise ArgumentError, <<~MSG.squish
|
|
779
791
|
The query constraints list on the `#{active_record}` model has more than 2
|
|
780
792
|
attributes. Active Record is unable to derive the query constraints
|
|
@@ -792,6 +804,8 @@ module ActiveRecord
|
|
|
792
804
|
MSG
|
|
793
805
|
end
|
|
794
806
|
|
|
807
|
+
return foreign_key if primary_query_constraints.include?(foreign_key)
|
|
808
|
+
|
|
795
809
|
first_key, last_key = primary_query_constraints
|
|
796
810
|
|
|
797
811
|
if first_key == owner_pk
|
|
@@ -857,8 +871,12 @@ module ActiveRecord
|
|
|
857
871
|
# klass option is necessary to support loading polymorphic associations
|
|
858
872
|
def association_primary_key(klass = nil)
|
|
859
873
|
if primary_key = options[:primary_key]
|
|
860
|
-
@association_primary_key ||=
|
|
861
|
-
|
|
874
|
+
@association_primary_key ||= if primary_key.is_a?(Array)
|
|
875
|
+
primary_key.map { |pk| pk.to_s.freeze }.freeze
|
|
876
|
+
else
|
|
877
|
+
-primary_key.to_s
|
|
878
|
+
end
|
|
879
|
+
elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
|
|
862
880
|
(klass || self.klass).composite_query_constraints_list
|
|
863
881
|
elsif (klass || self.klass).composite_primary_key?
|
|
864
882
|
# If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
|
|
@@ -81,6 +81,16 @@ module ActiveRecord
|
|
|
81
81
|
#
|
|
82
82
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
|
+
#
|
|
85
|
+
# When given a block, loads all records in the relation, if the relation
|
|
86
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
87
|
+
# Returns the number of records for which the block returns a truthy value.
|
|
88
|
+
#
|
|
89
|
+
# Person.count { |person| person.age > 21 }
|
|
90
|
+
# # => counts the number of people older that 21
|
|
91
|
+
#
|
|
92
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
93
|
+
# could result in performance issues.
|
|
84
94
|
def count(column_name = nil)
|
|
85
95
|
if block_given?
|
|
86
96
|
unless column_name.nil?
|
|
@@ -148,6 +158,17 @@ module ActiveRecord
|
|
|
148
158
|
# #calculate for examples with options.
|
|
149
159
|
#
|
|
150
160
|
# Person.sum(:age) # => 4562
|
|
161
|
+
#
|
|
162
|
+
# When given a block, loads all records in the relation, if the relation
|
|
163
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
164
|
+
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
+
# values:
|
|
166
|
+
#
|
|
167
|
+
# Person.sum { |person| person.age } # => 4562
|
|
168
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
|
+
#
|
|
170
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
171
|
+
# could result in performance issues.
|
|
151
172
|
def sum(initial_value_or_column = 0, &block)
|
|
152
173
|
if block_given?
|
|
153
174
|
map(&block).sum(initial_value_or_column)
|
|
@@ -260,7 +281,13 @@ module ActiveRecord
|
|
|
260
281
|
#
|
|
261
282
|
# See also #ids.
|
|
262
283
|
def pluck(*column_names)
|
|
263
|
-
|
|
284
|
+
if @none
|
|
285
|
+
if @async
|
|
286
|
+
return Promise::Complete.new([])
|
|
287
|
+
else
|
|
288
|
+
return []
|
|
289
|
+
end
|
|
290
|
+
end
|
|
264
291
|
|
|
265
292
|
if loaded? && all_attributes?(column_names)
|
|
266
293
|
result = records.pluck(*column_names)
|
|
@@ -102,7 +102,7 @@ module ActiveRecord
|
|
|
102
102
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
|
103
103
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
|
104
104
|
|
|
105
|
-
delegate :primary_key, :connection, to: :klass
|
|
105
|
+
delegate :primary_key, :connection, :transaction, to: :klass
|
|
106
106
|
|
|
107
107
|
module ClassSpecificRelation # :nodoc:
|
|
108
108
|
extend ActiveSupport::Concern
|
|
@@ -43,7 +43,12 @@ module ActiveRecord
|
|
|
43
43
|
|
|
44
44
|
def convert_to_id(value)
|
|
45
45
|
if value.is_a?(Base)
|
|
46
|
-
|
|
46
|
+
primary_key = primary_key(value)
|
|
47
|
+
if primary_key.is_a?(Array)
|
|
48
|
+
primary_key.map { |column| value._read_attribute(column) }
|
|
49
|
+
else
|
|
50
|
+
value._read_attribute(primary_key)
|
|
51
|
+
end
|
|
47
52
|
elsif value.is_a?(Relation)
|
|
48
53
|
value.select(primary_key(value))
|
|
49
54
|
else
|
|
@@ -1909,18 +1909,32 @@ module ActiveRecord
|
|
|
1909
1909
|
end
|
|
1910
1910
|
|
|
1911
1911
|
def column_references(order_args)
|
|
1912
|
-
|
|
1912
|
+
order_args.flat_map do |arg|
|
|
1913
1913
|
case arg
|
|
1914
1914
|
when String, Symbol
|
|
1915
|
-
arg
|
|
1915
|
+
extract_table_name_from(arg)
|
|
1916
1916
|
when Hash
|
|
1917
|
-
arg
|
|
1918
|
-
|
|
1917
|
+
arg
|
|
1918
|
+
.map do |key, value|
|
|
1919
|
+
case value
|
|
1920
|
+
when Hash
|
|
1921
|
+
key.to_s
|
|
1922
|
+
else
|
|
1923
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
|
1924
|
+
end
|
|
1925
|
+
end
|
|
1926
|
+
when Arel::Attribute
|
|
1927
|
+
arg.relation.name
|
|
1928
|
+
when Arel::Nodes::Ordering
|
|
1929
|
+
if arg.expr.is_a?(Arel::Attribute)
|
|
1930
|
+
arg.expr.relation.name
|
|
1919
1931
|
end
|
|
1920
1932
|
end
|
|
1921
|
-
end
|
|
1922
|
-
|
|
1923
|
-
|
|
1933
|
+
end.compact
|
|
1934
|
+
end
|
|
1935
|
+
|
|
1936
|
+
def extract_table_name_from(string)
|
|
1937
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
|
1924
1938
|
end
|
|
1925
1939
|
|
|
1926
1940
|
def order_column(field)
|
|
@@ -164,8 +164,8 @@ module ActiveRecord
|
|
|
164
164
|
#
|
|
165
165
|
# If creation failed because of a unique constraint, this method will
|
|
166
166
|
# assume it encountered a race condition and will try finding the record
|
|
167
|
-
# once more If somehow the second find still find
|
|
168
|
-
# concurrent DELETE happened, it will then raise an
|
|
167
|
+
# once more. If somehow the second find still does not find a record
|
|
168
|
+
# because a concurrent DELETE happened, it will then raise an
|
|
169
169
|
# ActiveRecord::RecordNotFound exception.
|
|
170
170
|
#
|
|
171
171
|
# Please note <b>this method is not atomic</b>, it runs first a SELECT,
|
|
@@ -291,6 +291,11 @@ module ActiveRecord
|
|
|
291
291
|
end
|
|
292
292
|
|
|
293
293
|
# Returns true if there are no records.
|
|
294
|
+
#
|
|
295
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
296
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
297
|
+
#
|
|
298
|
+
# posts.none?(Comment) # => true or false
|
|
294
299
|
def none?(*args)
|
|
295
300
|
return true if @none
|
|
296
301
|
|
|
@@ -299,6 +304,11 @@ module ActiveRecord
|
|
|
299
304
|
end
|
|
300
305
|
|
|
301
306
|
# Returns true if there are any records.
|
|
307
|
+
#
|
|
308
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
309
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
310
|
+
#
|
|
311
|
+
# posts.any?(Post) # => true or false
|
|
302
312
|
def any?(*args)
|
|
303
313
|
return false if @none
|
|
304
314
|
|
|
@@ -307,6 +317,11 @@ module ActiveRecord
|
|
|
307
317
|
end
|
|
308
318
|
|
|
309
319
|
# Returns true if there is exactly one record.
|
|
320
|
+
#
|
|
321
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
322
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
323
|
+
#
|
|
324
|
+
# posts.one?(Post) # => true or false
|
|
310
325
|
def one?(*args)
|
|
311
326
|
return false if @none
|
|
312
327
|
|
|
@@ -511,7 +526,12 @@ module ActiveRecord
|
|
|
511
526
|
|
|
512
527
|
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
513
528
|
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
514
|
-
|
|
529
|
+
key = if klass.composite_primary_key?
|
|
530
|
+
primary_key.map { |pk| table[pk] }
|
|
531
|
+
else
|
|
532
|
+
table[primary_key]
|
|
533
|
+
end
|
|
534
|
+
stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
|
|
515
535
|
klass.connection.update(stmt, "#{klass} Update All").tap { reset }
|
|
516
536
|
end
|
|
517
537
|
|
|
@@ -644,7 +664,12 @@ module ActiveRecord
|
|
|
644
664
|
|
|
645
665
|
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
646
666
|
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
647
|
-
|
|
667
|
+
key = if klass.composite_primary_key?
|
|
668
|
+
primary_key.map { |pk| table[pk] }
|
|
669
|
+
else
|
|
670
|
+
table[primary_key]
|
|
671
|
+
end
|
|
672
|
+
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
|
|
648
673
|
|
|
649
674
|
klass.connection.delete(stmt, "#{klass} Delete All").tap { reset }
|
|
650
675
|
end
|
|
@@ -960,7 +985,7 @@ module ActiveRecord
|
|
|
960
985
|
def exec_main_query(async: false)
|
|
961
986
|
if @none
|
|
962
987
|
if async
|
|
963
|
-
return
|
|
988
|
+
return FutureResult.wrap([])
|
|
964
989
|
else
|
|
965
990
|
return []
|
|
966
991
|
end
|
data/lib/active_record/result.rb
CHANGED
|
@@ -195,7 +195,7 @@ module ActiveRecord
|
|
|
195
195
|
EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
|
|
196
196
|
private_constant :EMPTY
|
|
197
197
|
|
|
198
|
-
EMPTY_ASYNC = FutureResult
|
|
198
|
+
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
|
199
199
|
private_constant :EMPTY_ASYNC
|
|
200
200
|
end
|
|
201
201
|
end
|
|
@@ -17,13 +17,27 @@ module ActiveRecord
|
|
|
17
17
|
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def async_sql_runtime
|
|
21
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def async_sql_runtime=(runtime)
|
|
25
|
+
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
|
|
26
|
+
end
|
|
27
|
+
|
|
20
28
|
def reset
|
|
21
29
|
rt, self.sql_runtime = sql_runtime, 0.0
|
|
30
|
+
self.async_sql_runtime = 0.0
|
|
22
31
|
rt
|
|
23
32
|
end
|
|
24
33
|
end
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
|
|
28
|
-
|
|
37
|
+
runtime = (finish - start) * 1_000.0
|
|
38
|
+
|
|
39
|
+
if payload[:async]
|
|
40
|
+
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
|
41
|
+
end
|
|
42
|
+
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
|
29
43
|
end
|
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
# number is inserted in to the schema migrations table so it doesn't need
|
|
7
7
|
# to be executed the next time.
|
|
8
8
|
class SchemaMigration # :nodoc:
|
|
9
|
-
class NullSchemaMigration
|
|
9
|
+
class NullSchemaMigration # :nodoc:
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
attr_reader :connection, :arel_table
|
|
@@ -53,7 +53,7 @@ module ActiveRecord
|
|
|
53
53
|
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
|
|
54
54
|
set_callback on, on == :initialize ? :after : :before do
|
|
55
55
|
if new_record? && !query_attribute(attribute)
|
|
56
|
-
|
|
56
|
+
send("#{attribute}=", self.class.generate_unique_secure_token(length: length))
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|