activerecord 7.1.5.1 → 7.2.2.1
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 +645 -2329
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +7 -1
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +85 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/enum.rb +18 -1
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +37 -55
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +69 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
@@ -125,11 +125,11 @@ module ActiveRecord
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def create_all
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
128
|
+
db_config = migration_connection.pool.db_config
|
129
|
+
|
130
|
+
each_local_configuration { |db_config| create(db_config) }
|
131
|
+
|
132
|
+
migration_class.establish_connection(db_config)
|
133
133
|
end
|
134
134
|
|
135
135
|
def setup_initial_database_yaml # :nodoc:
|
@@ -180,7 +180,7 @@ module ActiveRecord
|
|
180
180
|
each_current_configuration(env) do |db_config|
|
181
181
|
with_temporary_pool(db_config) do
|
182
182
|
begin
|
183
|
-
database_initialized =
|
183
|
+
database_initialized = migration_connection_pool.schema_migration.table_exists?
|
184
184
|
rescue ActiveRecord::NoDatabaseError
|
185
185
|
create(db_config)
|
186
186
|
retry
|
@@ -259,7 +259,7 @@ module ActiveRecord
|
|
259
259
|
|
260
260
|
check_target_version
|
261
261
|
|
262
|
-
|
262
|
+
migration_connection_pool.migration_context.migrate(target_version) do |migration|
|
263
263
|
if version.blank?
|
264
264
|
scope.blank? || scope == migration.scope
|
265
265
|
else
|
@@ -269,7 +269,7 @@ module ActiveRecord
|
|
269
269
|
Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
|
270
270
|
end
|
271
271
|
|
272
|
-
|
272
|
+
migration_connection_pool.schema_cache.clear!
|
273
273
|
ensure
|
274
274
|
Migration.verbose = verbose_was
|
275
275
|
end
|
@@ -277,9 +277,9 @@ module ActiveRecord
|
|
277
277
|
def db_configs_with_versions(environment = env) # :nodoc:
|
278
278
|
db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
|
279
279
|
|
280
|
-
|
281
|
-
db_config =
|
282
|
-
versions_to_run =
|
280
|
+
with_temporary_pool_for_each(env: environment) do |pool|
|
281
|
+
db_config = pool.db_config
|
282
|
+
versions_to_run = pool.migration_context.pending_migration_versions
|
283
283
|
target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
|
284
284
|
|
285
285
|
versions_to_run.each do |version|
|
@@ -292,15 +292,15 @@ module ActiveRecord
|
|
292
292
|
end
|
293
293
|
|
294
294
|
def migrate_status
|
295
|
-
unless
|
295
|
+
unless migration_connection_pool.schema_migration.table_exists?
|
296
296
|
Kernel.abort "Schema migrations table does not exist yet."
|
297
297
|
end
|
298
298
|
|
299
299
|
# output
|
300
|
-
puts "\ndatabase: #{
|
300
|
+
puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
|
301
301
|
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
|
302
302
|
puts "-" * 50
|
303
|
-
|
303
|
+
migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
|
304
304
|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
|
305
305
|
end
|
306
306
|
puts
|
@@ -381,7 +381,7 @@ module ActiveRecord
|
|
381
381
|
raise ArgumentError, "unknown format #{format.inspect}"
|
382
382
|
end
|
383
383
|
|
384
|
-
|
384
|
+
migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
|
385
385
|
ensure
|
386
386
|
Migration.verbose = verbose_was
|
387
387
|
end
|
@@ -393,11 +393,12 @@ module ActiveRecord
|
|
393
393
|
|
394
394
|
return true unless file && File.exist?(file)
|
395
395
|
|
396
|
-
|
397
|
-
|
398
|
-
return false unless
|
396
|
+
with_temporary_pool(db_config) do |pool|
|
397
|
+
internal_metadata = pool.internal_metadata
|
398
|
+
return false unless internal_metadata.enabled?
|
399
|
+
return false unless internal_metadata.table_exists?
|
399
400
|
|
400
|
-
|
401
|
+
internal_metadata[:schema_sha1] == schema_sha1(file)
|
401
402
|
end
|
402
403
|
end
|
403
404
|
|
@@ -408,7 +409,7 @@ module ActiveRecord
|
|
408
409
|
|
409
410
|
with_temporary_pool(db_config, clobber: true) do
|
410
411
|
if schema_up_to_date?(db_config, format, file)
|
411
|
-
truncate_tables(db_config)
|
412
|
+
truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
|
412
413
|
else
|
413
414
|
purge(db_config)
|
414
415
|
load_schema(db_config, format, file)
|
@@ -430,11 +431,11 @@ module ActiveRecord
|
|
430
431
|
case format
|
431
432
|
when :ruby
|
432
433
|
File.open(filename, "w:utf-8") do |file|
|
433
|
-
ActiveRecord::SchemaDumper.dump(
|
434
|
+
ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
|
434
435
|
end
|
435
436
|
when :sql
|
436
437
|
structure_dump(db_config, filename)
|
437
|
-
if
|
438
|
+
if migration_connection_pool.schema_migration.table_exists?
|
438
439
|
File.open(filename, "a") do |f|
|
439
440
|
f.puts migration_connection.dump_schema_information
|
440
441
|
f.print "\n"
|
@@ -456,14 +457,26 @@ module ActiveRecord
|
|
456
457
|
end
|
457
458
|
end
|
458
459
|
|
459
|
-
def cache_dump_filename(
|
460
|
-
|
461
|
-
|
460
|
+
def cache_dump_filename(db_config_or_name, schema_cache_path: nil)
|
461
|
+
if db_config_or_name.is_a?(DatabaseConfigurations::DatabaseConfig)
|
462
|
+
schema_cache_path ||
|
463
|
+
db_config_or_name.schema_cache_path ||
|
464
|
+
schema_cache_env ||
|
465
|
+
db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
|
462
466
|
else
|
463
|
-
|
464
|
-
|
467
|
+
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
468
|
+
Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 8.0. Pass a
|
469
|
+
`ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
|
470
|
+
MSG
|
465
471
|
|
466
|
-
|
472
|
+
filename = if ActiveRecord::Base.configurations.primary?(db_config_or_name)
|
473
|
+
"schema_cache.yml"
|
474
|
+
else
|
475
|
+
"#{db_config_or_name}_schema_cache.yml"
|
476
|
+
end
|
477
|
+
|
478
|
+
schema_cache_path || schema_cache_env || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
|
479
|
+
end
|
467
480
|
end
|
468
481
|
|
469
482
|
def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
|
@@ -495,29 +508,29 @@ module ActiveRecord
|
|
495
508
|
# Dumps the schema cache in YAML format for the connection into the file
|
496
509
|
#
|
497
510
|
# ==== Examples
|
498
|
-
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.
|
499
|
-
def dump_schema_cache(
|
500
|
-
|
511
|
+
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.lease_connection, "tmp/schema_dump.yaml")
|
512
|
+
def dump_schema_cache(conn_or_pool, filename)
|
513
|
+
conn_or_pool.schema_cache.dump_to(filename)
|
501
514
|
end
|
502
515
|
|
503
516
|
def clear_schema_cache(filename)
|
504
517
|
FileUtils.rm_f filename, verbose: false
|
505
518
|
end
|
506
519
|
|
507
|
-
def
|
520
|
+
def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
|
508
521
|
if name
|
509
522
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
|
510
|
-
|
523
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
511
524
|
else
|
512
525
|
ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
|
513
|
-
|
526
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
514
527
|
end
|
515
528
|
end
|
516
529
|
end
|
517
530
|
|
518
|
-
def with_temporary_connection(db_config, clobber: false) # :nodoc:
|
531
|
+
def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
|
519
532
|
with_temporary_pool(db_config, clobber: clobber) do |pool|
|
520
|
-
|
533
|
+
pool.with_connection(&block)
|
521
534
|
end
|
522
535
|
end
|
523
536
|
|
@@ -526,10 +539,25 @@ module ActiveRecord
|
|
526
539
|
end
|
527
540
|
|
528
541
|
def migration_connection # :nodoc:
|
529
|
-
migration_class.
|
542
|
+
migration_class.lease_connection
|
543
|
+
end
|
544
|
+
|
545
|
+
def migration_connection_pool # :nodoc:
|
546
|
+
migration_class.connection_pool
|
530
547
|
end
|
531
548
|
|
532
549
|
private
|
550
|
+
def schema_cache_env
|
551
|
+
if ENV["SCHEMA_CACHE"]
|
552
|
+
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
553
|
+
Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 8.0.
|
554
|
+
Configure the `:schema_cache_path` in the database configuration instead.
|
555
|
+
MSG
|
556
|
+
|
557
|
+
nil
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
533
561
|
def with_temporary_pool(db_config, clobber: false)
|
534
562
|
original_db_config = migration_class.connection_db_config
|
535
563
|
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
@@ -625,11 +653,11 @@ module ActiveRecord
|
|
625
653
|
|
626
654
|
def check_current_protected_environment!(db_config)
|
627
655
|
with_temporary_pool(db_config) do |pool|
|
628
|
-
|
629
|
-
current =
|
630
|
-
stored =
|
656
|
+
migration_context = pool.migration_context
|
657
|
+
current = migration_context.current_environment
|
658
|
+
stored = migration_context.last_stored_environment
|
631
659
|
|
632
|
-
if
|
660
|
+
if migration_context.protected_environment?
|
633
661
|
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
634
662
|
end
|
635
663
|
|
@@ -66,11 +66,12 @@ module ActiveRecord
|
|
66
66
|
attr_reader :db_config, :root
|
67
67
|
|
68
68
|
def connection
|
69
|
-
ActiveRecord::Base.
|
69
|
+
ActiveRecord::Base.lease_connection
|
70
70
|
end
|
71
71
|
|
72
72
|
def establish_connection(config = db_config)
|
73
73
|
ActiveRecord::Base.establish_connection(config)
|
74
|
+
connection.connect!
|
74
75
|
end
|
75
76
|
|
76
77
|
def run_cmd(cmd, args, out)
|
@@ -53,20 +53,6 @@ module ActiveRecord
|
|
53
53
|
self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
|
54
54
|
end
|
55
55
|
|
56
|
-
def fixture_path # :nodoc:
|
57
|
-
ActiveRecord.deprecator.warn(<<~WARNING)
|
58
|
-
TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
|
59
|
-
If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
|
60
|
-
the first path.
|
61
|
-
WARNING
|
62
|
-
fixture_paths.first
|
63
|
-
end
|
64
|
-
|
65
|
-
def fixture_path=(path) # :nodoc:
|
66
|
-
ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
|
67
|
-
self.fixture_paths = Array(path)
|
68
|
-
end
|
69
|
-
|
70
56
|
def fixtures(*fixture_set_names)
|
71
57
|
if fixture_set_names.first == :all
|
72
58
|
raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
|
@@ -79,7 +65,7 @@ module ActiveRecord
|
|
79
65
|
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
|
80
66
|
end
|
81
67
|
|
82
|
-
self.fixture_table_names
|
68
|
+
self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
|
83
69
|
setup_fixture_accessors(fixture_set_names)
|
84
70
|
end
|
85
71
|
|
@@ -110,45 +96,76 @@ module ActiveRecord
|
|
110
96
|
end
|
111
97
|
end
|
112
98
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
fixture_paths.first
|
99
|
+
# Generic fixture accessor for fixture names that may conflict with other methods.
|
100
|
+
#
|
101
|
+
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
|
102
|
+
# assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
|
103
|
+
def fixture(fixture_set_name, *fixture_names)
|
104
|
+
active_record_fixture(fixture_set_name, *fixture_names)
|
120
105
|
end
|
121
106
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def setup_fixtures(config = ActiveRecord::Base)
|
128
|
-
if pre_loaded_fixtures && !use_transactional_tests
|
129
|
-
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
|
107
|
+
private
|
108
|
+
def run_in_transaction?
|
109
|
+
use_transactional_tests &&
|
110
|
+
!self.class.uses_transaction?(name)
|
130
111
|
end
|
131
112
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
113
|
+
def setup_fixtures(config = ActiveRecord::Base)
|
114
|
+
if pre_loaded_fixtures && !use_transactional_tests
|
115
|
+
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
|
116
|
+
end
|
117
|
+
|
118
|
+
@fixture_cache = {}
|
119
|
+
@fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
|
120
|
+
@fixture_connection_pools = []
|
121
|
+
@@already_loaded_fixtures ||= {}
|
122
|
+
@connection_subscriber = nil
|
123
|
+
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
124
|
+
|
125
|
+
if run_in_transaction?
|
126
|
+
# Load fixtures once and begin transaction.
|
127
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
|
128
|
+
unless @loaded_fixtures
|
129
|
+
@@already_loaded_fixtures.clear
|
130
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
|
131
|
+
end
|
137
132
|
|
138
|
-
|
139
|
-
if run_in_transaction?
|
140
|
-
if @@already_loaded_fixtures[self.class]
|
141
|
-
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
133
|
+
setup_transactional_fixtures
|
142
134
|
else
|
135
|
+
# Load fixtures for every test.
|
136
|
+
ActiveRecord::FixtureSet.reset_cache
|
137
|
+
invalidate_already_loaded_fixtures
|
143
138
|
@loaded_fixtures = load_fixtures(config)
|
144
|
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
145
139
|
end
|
146
140
|
|
141
|
+
# Instantiate fixtures for every test if requested.
|
142
|
+
instantiate_fixtures if use_instantiated_fixtures
|
143
|
+
end
|
144
|
+
|
145
|
+
def teardown_fixtures
|
146
|
+
# Rollback changes if a transaction is active.
|
147
|
+
if run_in_transaction?
|
148
|
+
teardown_transactional_fixtures
|
149
|
+
else
|
150
|
+
ActiveRecord::FixtureSet.reset_cache
|
151
|
+
invalidate_already_loaded_fixtures
|
152
|
+
end
|
153
|
+
|
154
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
155
|
+
end
|
156
|
+
|
157
|
+
def invalidate_already_loaded_fixtures
|
158
|
+
@@already_loaded_fixtures.clear
|
159
|
+
end
|
160
|
+
|
161
|
+
def setup_transactional_fixtures
|
162
|
+
setup_shared_connection_pool
|
163
|
+
|
147
164
|
# Begin transactions for connections already established
|
148
|
-
@
|
149
|
-
@
|
150
|
-
|
151
|
-
|
165
|
+
@fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
|
166
|
+
@fixture_connection_pools.each do |pool|
|
167
|
+
pool.pin_connection!(lock_threads)
|
168
|
+
pool.lease_connection
|
152
169
|
end
|
153
170
|
|
154
171
|
# When connections are established in the future, begin a transaction too
|
@@ -157,59 +174,31 @@ module ActiveRecord
|
|
157
174
|
shard = payload[:shard] if payload.key?(:shard)
|
158
175
|
|
159
176
|
if connection_name
|
160
|
-
|
161
|
-
|
162
|
-
rescue ConnectionNotEstablished
|
163
|
-
connection = nil
|
164
|
-
end
|
165
|
-
|
166
|
-
if connection
|
177
|
+
pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
178
|
+
if pool
|
167
179
|
setup_shared_connection_pool
|
168
180
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
@
|
181
|
+
unless @fixture_connection_pools.include?(pool)
|
182
|
+
pool.pin_connection!(lock_threads)
|
183
|
+
pool.lease_connection
|
184
|
+
@fixture_connection_pools << pool
|
173
185
|
end
|
174
186
|
end
|
175
187
|
end
|
176
188
|
end
|
177
|
-
|
178
|
-
# Load fixtures for every test.
|
179
|
-
else
|
180
|
-
ActiveRecord::FixtureSet.reset_cache
|
181
|
-
@@already_loaded_fixtures[self.class] = nil
|
182
|
-
@loaded_fixtures = load_fixtures(config)
|
183
189
|
end
|
184
190
|
|
185
|
-
|
186
|
-
instantiate_fixtures if use_instantiated_fixtures
|
187
|
-
end
|
188
|
-
|
189
|
-
def teardown_fixtures
|
190
|
-
# Rollback changes if a transaction is active.
|
191
|
-
if run_in_transaction?
|
191
|
+
def teardown_transactional_fixtures
|
192
192
|
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
193
|
-
@
|
194
|
-
|
195
|
-
|
193
|
+
unless @fixture_connection_pools.map(&:unpin_connection!).all?
|
194
|
+
# Something caused the transaction to be committed or rolled back
|
195
|
+
# We can no longer trust the database is in a clean state.
|
196
|
+
@@already_loaded_fixtures.clear
|
196
197
|
end
|
197
|
-
@
|
198
|
+
@fixture_connection_pools.clear
|
198
199
|
teardown_shared_connection_pool
|
199
|
-
else
|
200
|
-
ActiveRecord::FixtureSet.reset_cache
|
201
200
|
end
|
202
201
|
|
203
|
-
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
204
|
-
end
|
205
|
-
|
206
|
-
def enlist_fixture_connections
|
207
|
-
setup_shared_connection_pool
|
208
|
-
|
209
|
-
ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
|
210
|
-
end
|
211
|
-
|
212
|
-
private
|
213
202
|
# Shares the writing connection pool with connections on
|
214
203
|
# other handlers.
|
215
204
|
#
|
@@ -272,22 +261,30 @@ module ActiveRecord
|
|
272
261
|
use_instantiated_fixtures != :no_instances
|
273
262
|
end
|
274
263
|
|
275
|
-
def method_missing(
|
276
|
-
if
|
277
|
-
|
264
|
+
def method_missing(method, ...)
|
265
|
+
if fixture_sets.key?(method.name)
|
266
|
+
active_record_fixture(method, ...)
|
278
267
|
else
|
279
268
|
super
|
280
269
|
end
|
281
270
|
end
|
282
271
|
|
283
|
-
def respond_to_missing?(
|
284
|
-
if include_private && fixture_sets.key?(name
|
272
|
+
def respond_to_missing?(method, include_private = false)
|
273
|
+
if include_private && fixture_sets.key?(method.name)
|
285
274
|
true
|
286
275
|
else
|
287
276
|
super
|
288
277
|
end
|
289
278
|
end
|
290
279
|
|
280
|
+
def active_record_fixture(fixture_set_name, *fixture_names)
|
281
|
+
if fs_name = fixture_sets[fixture_set_name.name]
|
282
|
+
access_fixture(fs_name, *fixture_names)
|
283
|
+
else
|
284
|
+
raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
291
288
|
def access_fixture(fs_name, *fixture_names)
|
292
289
|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
293
290
|
return_single_record = fixture_names.size == 1
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Assertions
|
5
|
+
module QueryAssertions
|
6
|
+
# Asserts that the number of SQL queries executed in the given block matches the expected count.
|
7
|
+
#
|
8
|
+
# # Check for exact number of queries
|
9
|
+
# assert_queries_count(1) { Post.first }
|
10
|
+
#
|
11
|
+
# # Check for any number of queries
|
12
|
+
# assert_queries_count { Post.first }
|
13
|
+
#
|
14
|
+
# If the +:include_schema+ option is provided, any queries (including schema related) are counted.
|
15
|
+
#
|
16
|
+
# assert_queries_count(1, include_schema: true) { Post.columns }
|
17
|
+
#
|
18
|
+
def assert_queries_count(count = nil, include_schema: false, &block)
|
19
|
+
ActiveRecord::Base.lease_connection.materialize_transactions
|
20
|
+
|
21
|
+
counter = SQLCounter.new
|
22
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
23
|
+
result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
|
24
|
+
queries = include_schema ? counter.log_all : counter.log
|
25
|
+
if count
|
26
|
+
assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
|
27
|
+
else
|
28
|
+
assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Asserts that no SQL queries are executed in the given block.
|
35
|
+
#
|
36
|
+
# assert_no_queries { post.comments }
|
37
|
+
#
|
38
|
+
# If the +:include_schema+ option is provided, any queries (including schema related) are counted.
|
39
|
+
#
|
40
|
+
# assert_no_queries(include_schema: true) { Post.columns }
|
41
|
+
#
|
42
|
+
def assert_no_queries(include_schema: false, &block)
|
43
|
+
assert_queries_count(0, include_schema: include_schema, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Asserts that the SQL queries executed in the given block match expected pattern.
|
47
|
+
#
|
48
|
+
# # Check for exact number of queries
|
49
|
+
# assert_queries_match(/LIMIT \?/, count: 1) { Post.first }
|
50
|
+
#
|
51
|
+
# # Check for any number of queries
|
52
|
+
# assert_queries_match(/LIMIT \?/) { Post.first }
|
53
|
+
#
|
54
|
+
# If the +:include_schema+ option is provided, any queries (including schema related)
|
55
|
+
# that match the matcher are considered.
|
56
|
+
#
|
57
|
+
# assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
|
58
|
+
#
|
59
|
+
def assert_queries_match(match, count: nil, include_schema: false, &block)
|
60
|
+
ActiveRecord::Base.lease_connection.materialize_transactions
|
61
|
+
|
62
|
+
counter = SQLCounter.new
|
63
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
64
|
+
result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
|
65
|
+
queries = include_schema ? counter.log_all : counter.log
|
66
|
+
matched_queries = queries.select { |query| match === query }
|
67
|
+
|
68
|
+
if count
|
69
|
+
assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
70
|
+
else
|
71
|
+
assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
72
|
+
end
|
73
|
+
|
74
|
+
result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Asserts that no SQL queries matching the pattern are executed in the given block.
|
79
|
+
#
|
80
|
+
# assert_no_queries_match(/SELECT/i) { post.comments }
|
81
|
+
#
|
82
|
+
# If the +:include_schema+ option is provided, any queries (including schema related)
|
83
|
+
# that match the matcher are counted.
|
84
|
+
#
|
85
|
+
# assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
|
86
|
+
#
|
87
|
+
def assert_no_queries_match(match, include_schema: false, &block)
|
88
|
+
assert_queries_match(match, count: 0, include_schema: include_schema, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
class SQLCounter # :nodoc:
|
92
|
+
attr_reader :log_full, :log_all
|
93
|
+
|
94
|
+
def initialize
|
95
|
+
@log_full = []
|
96
|
+
@log_all = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def log
|
100
|
+
@log_full.map(&:first)
|
101
|
+
end
|
102
|
+
|
103
|
+
def call(*, payload)
|
104
|
+
return if payload[:cached]
|
105
|
+
|
106
|
+
sql = payload[:sql]
|
107
|
+
@log_all << sql
|
108
|
+
|
109
|
+
unless payload[:name] == "SCHEMA"
|
110
|
+
bound_values = (payload[:binds] || []).map do |value|
|
111
|
+
value = value.value_for_database if value.respond_to?(:value_for_database)
|
112
|
+
value
|
113
|
+
end
|
114
|
+
|
115
|
+
@log_full << [sql, bound_values]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -77,7 +77,7 @@ module ActiveRecord
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def current_time_from_proper_timezone
|
80
|
-
|
80
|
+
with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now }
|
81
81
|
end
|
82
82
|
|
83
83
|
protected
|
@@ -162,7 +162,7 @@ module ActiveRecord
|
|
162
162
|
|
163
163
|
def max_updated_column_timestamp
|
164
164
|
timestamp_attributes_for_update_in_model
|
165
|
-
.filter_map { |attr| self[attr]
|
165
|
+
.filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
|
166
166
|
.max
|
167
167
|
end
|
168
168
|
|