activerecord 8.0.2 → 8.1.0
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 +555 -388
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +11 -1
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +3 -3
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +38 -24
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -4
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +413 -76
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +89 -21
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +25 -9
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +58 -34
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +13 -10
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +59 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +2 -2
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +31 -21
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +34 -5
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +23 -19
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +35 -0
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +35 -25
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +41 -24
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +44 -33
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +9 -10
- data/lib/active_record/relation.rb +38 -16
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +46 -18
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +34 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +68 -5
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +5 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -163,6 +163,18 @@ db_namespace = namespace :db do
|
|
|
163
163
|
desc "Resets your database using your migrations for the current environment"
|
|
164
164
|
task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"]
|
|
165
165
|
|
|
166
|
+
namespace :reset do
|
|
167
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
168
|
+
desc "Drop and recreate the #{name} database using migrations"
|
|
169
|
+
task name => :load_config do
|
|
170
|
+
db_namespace["drop:#{name}"].invoke
|
|
171
|
+
db_namespace["create:#{name}"].invoke
|
|
172
|
+
db_namespace["schema:dump:#{name}"].invoke
|
|
173
|
+
db_namespace["migrate:#{name}"].invoke
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
166
178
|
desc 'Run the "up" for a given migration VERSION.'
|
|
167
179
|
task up: :load_config do
|
|
168
180
|
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
|
|
@@ -333,7 +345,7 @@ db_namespace = namespace :db do
|
|
|
333
345
|
pending_migrations << pool.migration_context.open.pending_migrations
|
|
334
346
|
end
|
|
335
347
|
|
|
336
|
-
pending_migrations
|
|
348
|
+
pending_migrations.flatten!
|
|
337
349
|
|
|
338
350
|
if pending_migrations.any?
|
|
339
351
|
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
|
|
@@ -447,28 +459,23 @@ db_namespace = namespace :db do
|
|
|
447
459
|
namespace :schema do
|
|
448
460
|
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
|
|
449
461
|
task dump: :load_config do
|
|
450
|
-
ActiveRecord::Tasks::DatabaseTasks.
|
|
451
|
-
db_config = pool.db_config
|
|
452
|
-
schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
|
|
453
|
-
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
|
|
454
|
-
end
|
|
462
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_all
|
|
455
463
|
|
|
456
464
|
db_namespace["schema:dump"].reenable
|
|
457
465
|
end
|
|
458
466
|
|
|
459
467
|
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
|
|
460
468
|
task load: [:load_config, :check_protected_environments] do
|
|
461
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(
|
|
469
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
|
|
462
470
|
end
|
|
463
471
|
|
|
464
472
|
namespace :dump do
|
|
465
473
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
466
|
-
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on
|
|
474
|
+
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) for #{name} database"
|
|
467
475
|
task name => :load_config do
|
|
468
476
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
|
469
477
|
db_config = pool.db_config
|
|
470
|
-
|
|
471
|
-
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
|
|
478
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
472
479
|
end
|
|
473
480
|
|
|
474
481
|
db_namespace["schema:dump:#{name}"].reenable
|
|
@@ -478,12 +485,11 @@ db_namespace = namespace :db do
|
|
|
478
485
|
|
|
479
486
|
namespace :load do
|
|
480
487
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
|
481
|
-
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on
|
|
482
|
-
task name =>
|
|
488
|
+
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
|
|
489
|
+
task name => [:load_config, :check_protected_environments] do
|
|
483
490
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
|
484
491
|
db_config = pool.db_config
|
|
485
|
-
|
|
486
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
|
492
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
487
493
|
end
|
|
488
494
|
end
|
|
489
495
|
end
|
|
@@ -527,13 +533,12 @@ db_namespace = namespace :db do
|
|
|
527
533
|
end
|
|
528
534
|
|
|
529
535
|
namespace :test do
|
|
530
|
-
# desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on
|
|
536
|
+
# desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on configuration)"
|
|
531
537
|
task load_schema: %w(db:test:purge) do
|
|
532
538
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test") do |pool|
|
|
533
539
|
db_config = pool.db_config
|
|
534
540
|
ActiveRecord::Schema.verbose = false
|
|
535
|
-
|
|
536
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
|
541
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
537
542
|
end
|
|
538
543
|
end
|
|
539
544
|
|
|
@@ -558,8 +563,7 @@ db_namespace = namespace :db do
|
|
|
558
563
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool|
|
|
559
564
|
db_config = pool.db_config
|
|
560
565
|
ActiveRecord::Schema.verbose = false
|
|
561
|
-
|
|
562
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
|
566
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
|
563
567
|
end
|
|
564
568
|
end
|
|
565
569
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Railties # :nodoc:
|
|
5
|
+
module JobCheckpoints # :nodoc:
|
|
6
|
+
def checkpoint!
|
|
7
|
+
if ActiveRecord.all_open_transactions.any?
|
|
8
|
+
raise ActiveJob::Continuation::CheckpointError, "Cannot checkpoint job with open transactions"
|
|
9
|
+
else
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -5,19 +5,18 @@ require "active_record/runtime_registry"
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
module Railties # :nodoc:
|
|
7
7
|
module JobRuntime # :nodoc:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
result
|
|
16
|
-
end
|
|
17
|
-
else
|
|
18
|
-
super
|
|
8
|
+
def instrument(operation, payload = {}, &block) # :nodoc:
|
|
9
|
+
if operation == :perform && block
|
|
10
|
+
super(operation, payload) do
|
|
11
|
+
db_runtime_before_perform = ActiveRecord::RuntimeRegistry.stats.sql_runtime
|
|
12
|
+
result = block.call
|
|
13
|
+
payload[:db_runtime] = ActiveRecord::RuntimeRegistry.stats.sql_runtime - db_runtime_before_perform
|
|
14
|
+
result
|
|
19
15
|
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
20
18
|
end
|
|
19
|
+
end
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|
|
@@ -516,6 +516,8 @@ module ActiveRecord
|
|
|
516
516
|
|
|
517
517
|
def initialize(name, scope, options, active_record)
|
|
518
518
|
super
|
|
519
|
+
|
|
520
|
+
@validated = false
|
|
519
521
|
@type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
|
|
520
522
|
@foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
|
|
521
523
|
@join_table = nil
|
|
@@ -534,6 +536,8 @@ module ActiveRecord
|
|
|
534
536
|
options[:query_constraints] = options.delete(:foreign_key)
|
|
535
537
|
end
|
|
536
538
|
|
|
539
|
+
@deprecated = !!options[:deprecated]
|
|
540
|
+
|
|
537
541
|
ensure_option_not_given_as_class!(:class_name)
|
|
538
542
|
end
|
|
539
543
|
|
|
@@ -616,6 +620,8 @@ module ActiveRecord
|
|
|
616
620
|
end
|
|
617
621
|
|
|
618
622
|
def check_validity!
|
|
623
|
+
return if @validated
|
|
624
|
+
|
|
619
625
|
check_validity_of_inverse!
|
|
620
626
|
|
|
621
627
|
if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
|
|
@@ -625,6 +631,8 @@ module ActiveRecord
|
|
|
625
631
|
raise CompositePrimaryKeyMismatchError.new(self)
|
|
626
632
|
end
|
|
627
633
|
end
|
|
634
|
+
|
|
635
|
+
@validated = true
|
|
628
636
|
end
|
|
629
637
|
|
|
630
638
|
def check_eager_loadable!
|
|
@@ -742,6 +750,10 @@ module ActiveRecord
|
|
|
742
750
|
Array(options[:extend])
|
|
743
751
|
end
|
|
744
752
|
|
|
753
|
+
def deprecated?
|
|
754
|
+
@deprecated
|
|
755
|
+
end
|
|
756
|
+
|
|
745
757
|
private
|
|
746
758
|
# Attempts to find the inverse association name automatically.
|
|
747
759
|
# If it cannot find a suitable inverse association name, it returns
|
|
@@ -975,6 +987,8 @@ module ActiveRecord
|
|
|
975
987
|
|
|
976
988
|
def initialize(delegate_reflection)
|
|
977
989
|
super()
|
|
990
|
+
|
|
991
|
+
@validated = false
|
|
978
992
|
@delegate_reflection = delegate_reflection
|
|
979
993
|
@klass = delegate_reflection.options[:anonymous_class]
|
|
980
994
|
@source_reflection_name = delegate_reflection.options[:source]
|
|
@@ -1138,6 +1152,8 @@ module ActiveRecord
|
|
|
1138
1152
|
end
|
|
1139
1153
|
|
|
1140
1154
|
def check_validity!
|
|
1155
|
+
return if @validated
|
|
1156
|
+
|
|
1141
1157
|
if through_reflection.nil?
|
|
1142
1158
|
raise HasManyThroughAssociationNotFoundError.new(active_record, self)
|
|
1143
1159
|
end
|
|
@@ -1175,6 +1191,8 @@ module ActiveRecord
|
|
|
1175
1191
|
end
|
|
1176
1192
|
|
|
1177
1193
|
check_validity_of_inverse!
|
|
1194
|
+
|
|
1195
|
+
@validated = true
|
|
1178
1196
|
end
|
|
1179
1197
|
|
|
1180
1198
|
def constraints
|
|
@@ -1195,6 +1213,10 @@ module ActiveRecord
|
|
|
1195
1213
|
collect_join_reflections(seed + [self])
|
|
1196
1214
|
end
|
|
1197
1215
|
|
|
1216
|
+
def deprecated_nested_reflections
|
|
1217
|
+
@deprecated_nested_reflections ||= collect_deprecated_nested_reflections
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1198
1220
|
protected
|
|
1199
1221
|
def actual_source_reflection # FIXME: this is a horrible name
|
|
1200
1222
|
source_reflection.actual_source_reflection
|
|
@@ -1219,6 +1241,19 @@ module ActiveRecord
|
|
|
1219
1241
|
options[:source_type] || source_reflection.class_name
|
|
1220
1242
|
end
|
|
1221
1243
|
|
|
1244
|
+
def collect_deprecated_nested_reflections
|
|
1245
|
+
result = []
|
|
1246
|
+
[through_reflection, source_reflection].each do |reflection|
|
|
1247
|
+
result << reflection if reflection.deprecated?
|
|
1248
|
+
# Both the through and the source reflections could be through
|
|
1249
|
+
# themselves. Nesting can go an arbitrary number of levels down.
|
|
1250
|
+
if reflection.through_reflection?
|
|
1251
|
+
result.concat(reflection.deprecated_nested_reflections)
|
|
1252
|
+
end
|
|
1253
|
+
end
|
|
1254
|
+
result
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1222
1257
|
delegate_methods = AssociationReflection.public_instance_methods -
|
|
1223
1258
|
public_instance_methods
|
|
1224
1259
|
|
|
@@ -435,35 +435,49 @@ module ActiveRecord
|
|
|
435
435
|
if load
|
|
436
436
|
records = batch_relation.records
|
|
437
437
|
values = records.pluck(*cursor)
|
|
438
|
-
|
|
438
|
+
values_size = values.size
|
|
439
|
+
values_last = values.last
|
|
440
|
+
yielded_relation = rewhere(cursor => values)
|
|
439
441
|
yielded_relation.load_records(records)
|
|
440
442
|
elsif (empty_scope && use_ranges != false) || use_ranges
|
|
441
|
-
|
|
443
|
+
# Efficiently peak at the last value for the next batch using offset and limit.
|
|
444
|
+
values_size = batch_limit
|
|
445
|
+
values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
|
|
446
|
+
|
|
447
|
+
# If the last value is not found using offset, there is at most one more batch of size < batch_limit.
|
|
448
|
+
# Retry by getting the whole list of remaining values so that we have the exact size and last value.
|
|
449
|
+
unless values_last
|
|
450
|
+
values = batch_relation.pluck(*cursor)
|
|
451
|
+
values_size = values.size
|
|
452
|
+
values_last = values.last
|
|
453
|
+
end
|
|
442
454
|
|
|
443
|
-
|
|
444
|
-
if
|
|
445
|
-
yielded_relation = apply_finish_limit(batch_relation, cursor,
|
|
455
|
+
# Finally, build the yielded relation if at least one value found.
|
|
456
|
+
if values_last
|
|
457
|
+
yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
|
|
446
458
|
yielded_relation = yielded_relation.except(:limit, :order)
|
|
447
459
|
yielded_relation.skip_query_cache!(false)
|
|
448
460
|
end
|
|
449
461
|
else
|
|
450
462
|
values = batch_relation.pluck(*cursor)
|
|
451
|
-
|
|
463
|
+
values_size = values.size
|
|
464
|
+
values_last = values.last
|
|
465
|
+
yielded_relation = rewhere(cursor => values)
|
|
452
466
|
end
|
|
453
467
|
|
|
454
|
-
break if
|
|
468
|
+
break if values_size == 0
|
|
455
469
|
|
|
456
|
-
if
|
|
470
|
+
if [values_last].flatten.any?(nil)
|
|
457
471
|
raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
|
|
458
472
|
"or some columns contain nil."
|
|
459
473
|
end
|
|
460
474
|
|
|
461
475
|
yield yielded_relation
|
|
462
476
|
|
|
463
|
-
break if
|
|
477
|
+
break if values_size < batch_limit
|
|
464
478
|
|
|
465
479
|
if limit_value
|
|
466
|
-
remaining -=
|
|
480
|
+
remaining -= values_size
|
|
467
481
|
|
|
468
482
|
if remaining == 0
|
|
469
483
|
# Saves a useless iteration when the limit is a multiple of the
|
|
@@ -481,7 +495,7 @@ module ActiveRecord
|
|
|
481
495
|
end
|
|
482
496
|
operators << (last_order == :desc ? :lt : :gt)
|
|
483
497
|
|
|
484
|
-
cursor_value =
|
|
498
|
+
cursor_value = values_last
|
|
485
499
|
batch_relation = batch_condition(relation, cursor, cursor_value, operators)
|
|
486
500
|
end
|
|
487
501
|
|
|
@@ -60,37 +60,37 @@ module ActiveRecord
|
|
|
60
60
|
# Person.distinct.count(:age)
|
|
61
61
|
# # => counts the number of different age values
|
|
62
62
|
#
|
|
63
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
64
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
65
65
|
# and the values are the respective amounts:
|
|
66
66
|
#
|
|
67
67
|
# Person.group(:city).count
|
|
68
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
69
69
|
#
|
|
70
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
71
71
|
# keys are an array containing the individual values of each column and the value
|
|
72
|
-
# of each key would be the
|
|
72
|
+
# of each key would be the count.
|
|
73
73
|
#
|
|
74
74
|
# Article.group(:status, :category).count
|
|
75
75
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
76
76
|
#
|
|
77
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
78
|
#
|
|
79
79
|
# Person.select(:age).count
|
|
80
80
|
# # => counts the number of different age values
|
|
81
81
|
#
|
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
|
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
84
|
#
|
|
85
|
-
# When given a block,
|
|
86
|
-
#
|
|
87
|
-
# Returns the number of records for which the block returns a truthy value.
|
|
85
|
+
# When given a block, calls the block with each record in the relation and
|
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
|
88
87
|
#
|
|
89
88
|
# Person.count { |person| person.age > 21 }
|
|
90
89
|
# # => counts the number of people older that 21
|
|
91
90
|
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
|
93
|
+
# relation, loading all records could result in performance issues.
|
|
94
94
|
def count(column_name = nil)
|
|
95
95
|
if block_given?
|
|
96
96
|
unless column_name.nil?
|
|
@@ -159,16 +159,15 @@ module ActiveRecord
|
|
|
159
159
|
#
|
|
160
160
|
# Person.sum(:age) # => 4562
|
|
161
161
|
#
|
|
162
|
-
# When given a block,
|
|
163
|
-
#
|
|
164
|
-
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
-
# values:
|
|
162
|
+
# When given a block, calls the block with each record in the relation and
|
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
|
166
164
|
#
|
|
167
165
|
# Person.sum { |person| person.age } # => 4562
|
|
168
166
|
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
167
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
|
170
|
+
# relation, loading all records could result in performance issues.
|
|
172
171
|
def sum(initial_value_or_column = 0, &block)
|
|
173
172
|
if block_given?
|
|
174
173
|
map(&block).sum(initial_value_or_column)
|
|
@@ -287,6 +286,11 @@ module ActiveRecord
|
|
|
287
286
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
288
287
|
# # => ['0', '27761', '173']
|
|
289
288
|
#
|
|
289
|
+
# Be aware that #pluck ignores any previous select clauses
|
|
290
|
+
#
|
|
291
|
+
# Person.select(:name).pluck(:id)
|
|
292
|
+
# # SELECT people.id FROM people
|
|
293
|
+
#
|
|
290
294
|
# See also #ids.
|
|
291
295
|
def pluck(*column_names)
|
|
292
296
|
if @none
|
|
@@ -315,7 +319,7 @@ module ActiveRecord
|
|
|
315
319
|
columns = relation.arel_columns(column_names)
|
|
316
320
|
relation.select_values = columns
|
|
317
321
|
result = skip_query_cache_if_necessary do
|
|
318
|
-
if where_clause.contradiction?
|
|
322
|
+
if where_clause.contradiction? && !possible_aggregation?(column_names)
|
|
319
323
|
ActiveRecord::Result.empty(async: @async)
|
|
320
324
|
else
|
|
321
325
|
model.with_connection do |c|
|
|
@@ -418,7 +422,7 @@ module ActiveRecord
|
|
|
418
422
|
when :all
|
|
419
423
|
Arel.star
|
|
420
424
|
else
|
|
421
|
-
arel_column(column_name)
|
|
425
|
+
arel_column(column_name.to_s)
|
|
422
426
|
end
|
|
423
427
|
end
|
|
424
428
|
|
|
@@ -462,6 +466,16 @@ module ActiveRecord
|
|
|
462
466
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
463
467
|
end
|
|
464
468
|
|
|
469
|
+
def possible_aggregation?(column_names)
|
|
470
|
+
column_names.all? do |column_name|
|
|
471
|
+
if column_name.is_a?(String)
|
|
472
|
+
column_name.include?("(")
|
|
473
|
+
else
|
|
474
|
+
Arel.arel_node?(column_name)
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
465
479
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
466
480
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
467
481
|
end
|
|
@@ -513,7 +527,6 @@ module ActiveRecord
|
|
|
513
527
|
|
|
514
528
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
515
529
|
group_fields = group_values
|
|
516
|
-
group_fields = group_fields.uniq if group_fields.size > 1
|
|
517
530
|
|
|
518
531
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
519
532
|
association = model._reflect_on_association(group_fields.first)
|
|
@@ -536,7 +549,7 @@ module ActiveRecord
|
|
|
536
549
|
column = relation.aggregate_column(column_name)
|
|
537
550
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
538
551
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
539
|
-
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
552
|
+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
540
553
|
|
|
541
554
|
select_values = [select_value]
|
|
542
555
|
select_values += self.select_values unless having_clause.empty?
|
|
@@ -663,6 +676,7 @@ module ActiveRecord
|
|
|
663
676
|
if column_name == :all
|
|
664
677
|
column_alias = Arel.star
|
|
665
678
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
679
|
+
relation.unscope!(:order)
|
|
666
680
|
else
|
|
667
681
|
column_alias = Arel.sql("count_column")
|
|
668
682
|
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
@@ -671,11 +685,7 @@ module ActiveRecord
|
|
|
671
685
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
672
686
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
673
687
|
|
|
674
|
-
|
|
675
|
-
relation.unscope(:order).build_subquery(subquery_alias, select_value)
|
|
676
|
-
else
|
|
677
|
-
relation.build_subquery(subquery_alias, select_value)
|
|
678
|
-
end
|
|
688
|
+
relation.build_subquery(subquery_alias, select_value)
|
|
679
689
|
end
|
|
680
690
|
end
|
|
681
691
|
end
|
|
@@ -24,22 +24,22 @@ module ActiveRecord
|
|
|
24
24
|
# TravelRoute.primary_key = [:origin, :destination]
|
|
25
25
|
#
|
|
26
26
|
# TravelRoute.find(["Ottawa", "London"])
|
|
27
|
-
# => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
27
|
+
# # => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
28
28
|
#
|
|
29
29
|
# TravelRoute.find([["Paris", "Montreal"]])
|
|
30
|
-
# => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
30
|
+
# # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
31
31
|
#
|
|
32
32
|
# TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
|
|
33
|
-
# => [
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
33
|
+
# # => [
|
|
34
|
+
# # #<TravelRoute origin: "New York", destination: "Las Vegas">,
|
|
35
|
+
# # #<TravelRoute origin: "New York", destination: "Portland">
|
|
36
|
+
# # ]
|
|
37
37
|
#
|
|
38
38
|
# TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
|
|
39
|
-
# => [
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
39
|
+
# # => [
|
|
40
|
+
# # #<TravelRoute origin: "Berlin", destination: "London">,
|
|
41
|
+
# # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
|
|
42
|
+
# # ]
|
|
43
43
|
#
|
|
44
44
|
# NOTE: The returned records are in the same order as the ids you provide.
|
|
45
45
|
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
|
|
@@ -141,7 +141,7 @@ module ActiveRecord
|
|
|
141
141
|
#
|
|
142
142
|
# Product.where(["price = %?", price]).sole
|
|
143
143
|
def sole
|
|
144
|
-
found, undesired =
|
|
144
|
+
found, undesired = take(2)
|
|
145
145
|
|
|
146
146
|
if found.nil?
|
|
147
147
|
raise_record_not_found_exception!
|
|
@@ -424,12 +424,13 @@ module ActiveRecord
|
|
|
424
424
|
error << " with#{conditions}" if conditions
|
|
425
425
|
raise RecordNotFound.new(error, name, key)
|
|
426
426
|
elsif Array.wrap(ids).size == 1
|
|
427
|
-
|
|
427
|
+
id = Array.wrap(ids)[0]
|
|
428
|
+
error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
|
|
428
429
|
raise RecordNotFound.new(error, name, key, ids)
|
|
429
430
|
else
|
|
430
431
|
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
|
|
431
|
-
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
|
432
|
-
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
|
|
432
|
+
error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
|
433
|
+
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
|
|
433
434
|
raise RecordNotFound.new(error, name, key, ids)
|
|
434
435
|
end
|
|
435
436
|
end
|
|
@@ -441,7 +442,7 @@ module ActiveRecord
|
|
|
441
442
|
if distinct_value && offset_value
|
|
442
443
|
relation = except(:order).limit!(1)
|
|
443
444
|
else
|
|
444
|
-
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
|
|
445
|
+
relation = except(:select, :distinct, :order)._select!(Arel.sql(ONE_AS_ONE, retryable: true)).limit!(1)
|
|
445
446
|
end
|
|
446
447
|
|
|
447
448
|
case conditions
|
|
@@ -638,24 +639,40 @@ module ActiveRecord
|
|
|
638
639
|
end
|
|
639
640
|
|
|
640
641
|
def ordered_relation
|
|
641
|
-
if order_values.empty?
|
|
642
|
-
|
|
642
|
+
if order_values.empty?
|
|
643
|
+
if !_order_columns.empty?
|
|
644
|
+
return order(_order_columns.map { |column| table[column].asc })
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
if ActiveRecord.raise_on_missing_required_finder_order_columns
|
|
648
|
+
raise MissingRequiredOrderError, <<~MSG.squish
|
|
649
|
+
Relation has no order values, and #{model} has no order columns to use as a default.
|
|
650
|
+
Set at least one of `implicit_order_column`, `query_constraints` or `primary_key` on
|
|
651
|
+
the model when no `order `is specified on the relation.
|
|
652
|
+
MSG
|
|
653
|
+
else
|
|
654
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
655
|
+
Calling order dependent finder methods (e.g. `#first`, `#second`) without `order` values on the relation,
|
|
656
|
+
and on a model (#{model}) that does not have any order columns (`implicit_order_column`, `query_constraints`,
|
|
657
|
+
or `primary_key`) to fall back on is deprecated and will raise `ActiveRecord::MissingRequiredOrderError`
|
|
658
|
+
in Rails 8.2.
|
|
659
|
+
MSG
|
|
660
|
+
|
|
661
|
+
self
|
|
662
|
+
end
|
|
643
663
|
else
|
|
644
664
|
self
|
|
645
665
|
end
|
|
646
666
|
end
|
|
647
667
|
|
|
648
668
|
def _order_columns
|
|
649
|
-
|
|
669
|
+
columns = Array(model.implicit_order_column)
|
|
650
670
|
|
|
651
|
-
|
|
652
|
-
oc << model.query_constraints_list if model.query_constraints_list
|
|
671
|
+
return columns.compact if columns.length.positive? && columns.last.nil?
|
|
653
672
|
|
|
654
|
-
|
|
655
|
-
oc << model.primary_key
|
|
656
|
-
end
|
|
673
|
+
columns += Array(model.query_constraints_list || model.primary_key)
|
|
657
674
|
|
|
658
|
-
|
|
675
|
+
columns.uniq.compact
|
|
659
676
|
end
|
|
660
677
|
end
|
|
661
678
|
end
|
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
class PredicateBuilder
|
|
5
5
|
class AssociationQueryValue # :nodoc:
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
6
|
+
def initialize(reflection, value)
|
|
7
|
+
@reflection = reflection
|
|
8
8
|
@value = value
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def queries
|
|
12
|
-
if
|
|
12
|
+
if reflection.join_foreign_key.is_a?(Array)
|
|
13
13
|
id_list = ids
|
|
14
14
|
id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
|
|
15
15
|
|
|
16
|
-
id_list.map { |ids_set|
|
|
16
|
+
id_list.map { |ids_set| reflection.join_foreign_key.zip(ids_set).to_h }
|
|
17
17
|
else
|
|
18
|
-
[
|
|
18
|
+
[ reflection.join_foreign_key => ids ]
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
|
-
attr_reader :
|
|
23
|
+
attr_reader :reflection, :value
|
|
24
24
|
|
|
25
25
|
def ids
|
|
26
26
|
case value
|
|
@@ -37,15 +37,15 @@ module ActiveRecord
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def primary_key
|
|
40
|
-
|
|
40
|
+
reflection.join_primary_key
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def primary_type
|
|
44
|
-
|
|
44
|
+
reflection.join_primary_type
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def polymorphic_name
|
|
48
|
-
|
|
48
|
+
reflection.polymorphic_name
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def select_clause?
|