activerecord 7.1.3.2 → 7.2.0.beta1
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 +507 -2123
- 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 +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- 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 +4 -2
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -7
- 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 +34 -11
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- 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 +87 -58
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +14 -30
- data/lib/active_record/base.rb +2 -3
- 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 +248 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
- 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 +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- 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 +7 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- 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/cidr.rb +6 -0
- 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 +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- 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 +109 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +59 -38
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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/encryption/scheme.rb +8 -4
- data/lib/active_record/enum.rb +11 -2
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- 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 +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +34 -69
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +52 -64
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +98 -37
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- 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/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -43
- 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 +500 -66
- data/lib/active_record/result.rb +32 -45
- 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/signed_id.rb +11 -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 +70 -42
- 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 +82 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +4 -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 +68 -0
- data/lib/active_record/transactions.rb +43 -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 +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- 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 +8 -3
- 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/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- metadata +17 -12
@@ -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:
|
@@ -179,7 +179,7 @@ module ActiveRecord
|
|
179
179
|
each_current_configuration(env) do |db_config|
|
180
180
|
with_temporary_pool(db_config) do
|
181
181
|
begin
|
182
|
-
database_initialized =
|
182
|
+
database_initialized = migration_connection_pool.schema_migration.table_exists?
|
183
183
|
rescue ActiveRecord::NoDatabaseError
|
184
184
|
create(db_config)
|
185
185
|
retry
|
@@ -240,7 +240,7 @@ module ActiveRecord
|
|
240
240
|
|
241
241
|
check_target_version
|
242
242
|
|
243
|
-
|
243
|
+
migration_connection_pool.migration_context.migrate(target_version) do |migration|
|
244
244
|
if version.blank?
|
245
245
|
scope.blank? || scope == migration.scope
|
246
246
|
else
|
@@ -250,17 +250,17 @@ module ActiveRecord
|
|
250
250
|
Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
|
251
251
|
end
|
252
252
|
|
253
|
-
|
253
|
+
migration_connection_pool.schema_cache.clear!
|
254
254
|
ensure
|
255
255
|
Migration.verbose = verbose_was
|
256
256
|
end
|
257
257
|
|
258
|
-
def db_configs_with_versions
|
258
|
+
def db_configs_with_versions # :nodoc:
|
259
259
|
db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
|
260
260
|
|
261
|
-
|
262
|
-
db_config =
|
263
|
-
versions_to_run =
|
261
|
+
with_temporary_pool_for_each do |pool|
|
262
|
+
db_config = pool.db_config
|
263
|
+
versions_to_run = pool.migration_context.pending_migration_versions
|
264
264
|
target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
|
265
265
|
|
266
266
|
versions_to_run.each do |version|
|
@@ -273,15 +273,15 @@ module ActiveRecord
|
|
273
273
|
end
|
274
274
|
|
275
275
|
def migrate_status
|
276
|
-
unless
|
276
|
+
unless migration_connection_pool.schema_migration.table_exists?
|
277
277
|
Kernel.abort "Schema migrations table does not exist yet."
|
278
278
|
end
|
279
279
|
|
280
280
|
# output
|
281
|
-
puts "\ndatabase: #{
|
281
|
+
puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
|
282
282
|
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
|
283
283
|
puts "-" * 50
|
284
|
-
|
284
|
+
migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
|
285
285
|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
|
286
286
|
end
|
287
287
|
puts
|
@@ -362,7 +362,7 @@ module ActiveRecord
|
|
362
362
|
raise ArgumentError, "unknown format #{format.inspect}"
|
363
363
|
end
|
364
364
|
|
365
|
-
|
365
|
+
migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
|
366
366
|
ensure
|
367
367
|
Migration.verbose = verbose_was
|
368
368
|
end
|
@@ -374,11 +374,12 @@ module ActiveRecord
|
|
374
374
|
|
375
375
|
return true unless file && File.exist?(file)
|
376
376
|
|
377
|
-
|
378
|
-
|
379
|
-
return false unless
|
377
|
+
with_temporary_pool(db_config) do |pool|
|
378
|
+
internal_metadata = pool.internal_metadata
|
379
|
+
return false unless internal_metadata.enabled?
|
380
|
+
return false unless internal_metadata.table_exists?
|
380
381
|
|
381
|
-
|
382
|
+
internal_metadata[:schema_sha1] == schema_sha1(file)
|
382
383
|
end
|
383
384
|
end
|
384
385
|
|
@@ -389,7 +390,7 @@ module ActiveRecord
|
|
389
390
|
|
390
391
|
with_temporary_pool(db_config, clobber: true) do
|
391
392
|
if schema_up_to_date?(db_config, format, file)
|
392
|
-
truncate_tables(db_config)
|
393
|
+
truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
|
393
394
|
else
|
394
395
|
purge(db_config)
|
395
396
|
load_schema(db_config, format, file)
|
@@ -411,11 +412,11 @@ module ActiveRecord
|
|
411
412
|
case format
|
412
413
|
when :ruby
|
413
414
|
File.open(filename, "w:utf-8") do |file|
|
414
|
-
ActiveRecord::SchemaDumper.dump(
|
415
|
+
ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
|
415
416
|
end
|
416
417
|
when :sql
|
417
418
|
structure_dump(db_config, filename)
|
418
|
-
if
|
419
|
+
if migration_connection_pool.schema_migration.table_exists?
|
419
420
|
File.open(filename, "a") do |f|
|
420
421
|
f.puts migration_connection.dump_schema_information
|
421
422
|
f.print "\n"
|
@@ -437,14 +438,26 @@ module ActiveRecord
|
|
437
438
|
end
|
438
439
|
end
|
439
440
|
|
440
|
-
def cache_dump_filename(
|
441
|
-
|
442
|
-
|
441
|
+
def cache_dump_filename(db_config_or_name, schema_cache_path: nil)
|
442
|
+
if db_config_or_name.is_a?(DatabaseConfigurations::DatabaseConfig)
|
443
|
+
schema_cache_path ||
|
444
|
+
db_config_or_name.schema_cache_path ||
|
445
|
+
schema_cache_env ||
|
446
|
+
db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
|
443
447
|
else
|
444
|
-
|
445
|
-
|
448
|
+
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
449
|
+
Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 7.3. Pass a
|
450
|
+
`ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
|
451
|
+
MSG
|
446
452
|
|
447
|
-
|
453
|
+
filename = if ActiveRecord::Base.configurations.primary?(db_config_or_name)
|
454
|
+
"schema_cache.yml"
|
455
|
+
else
|
456
|
+
"#{db_config_or_name}_schema_cache.yml"
|
457
|
+
end
|
458
|
+
|
459
|
+
schema_cache_path || schema_cache_env || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
|
460
|
+
end
|
448
461
|
end
|
449
462
|
|
450
463
|
def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
|
@@ -476,29 +489,29 @@ module ActiveRecord
|
|
476
489
|
# Dumps the schema cache in YAML format for the connection into the file
|
477
490
|
#
|
478
491
|
# ==== Examples
|
479
|
-
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.
|
480
|
-
def dump_schema_cache(
|
481
|
-
|
492
|
+
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.lease_connection, "tmp/schema_dump.yaml")
|
493
|
+
def dump_schema_cache(conn_or_pool, filename)
|
494
|
+
conn_or_pool.schema_cache.dump_to(filename)
|
482
495
|
end
|
483
496
|
|
484
497
|
def clear_schema_cache(filename)
|
485
498
|
FileUtils.rm_f filename, verbose: false
|
486
499
|
end
|
487
500
|
|
488
|
-
def
|
501
|
+
def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
|
489
502
|
if name
|
490
503
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
|
491
|
-
|
504
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
492
505
|
else
|
493
506
|
ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
|
494
|
-
|
507
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
495
508
|
end
|
496
509
|
end
|
497
510
|
end
|
498
511
|
|
499
|
-
def with_temporary_connection(db_config, clobber: false) # :nodoc:
|
512
|
+
def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
|
500
513
|
with_temporary_pool(db_config, clobber: clobber) do |pool|
|
501
|
-
|
514
|
+
pool.with_connection(&block)
|
502
515
|
end
|
503
516
|
end
|
504
517
|
|
@@ -507,10 +520,25 @@ module ActiveRecord
|
|
507
520
|
end
|
508
521
|
|
509
522
|
def migration_connection # :nodoc:
|
510
|
-
migration_class.
|
523
|
+
migration_class.lease_connection
|
524
|
+
end
|
525
|
+
|
526
|
+
def migration_connection_pool # :nodoc:
|
527
|
+
migration_class.connection_pool
|
511
528
|
end
|
512
529
|
|
513
530
|
private
|
531
|
+
def schema_cache_env
|
532
|
+
if ENV["SCHEMA_CACHE"]
|
533
|
+
ActiveRecord.deprecator.warn(<<~MSG.squish)
|
534
|
+
Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 7.3.
|
535
|
+
Configure the `:schema_cache_path` in the database configuration instead.
|
536
|
+
MSG
|
537
|
+
|
538
|
+
nil
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
514
542
|
def with_temporary_pool(db_config, clobber: false)
|
515
543
|
original_db_config = migration_class.connection_db_config
|
516
544
|
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
@@ -603,11 +631,11 @@ module ActiveRecord
|
|
603
631
|
|
604
632
|
def check_current_protected_environment!(db_config)
|
605
633
|
with_temporary_pool(db_config) do |pool|
|
606
|
-
|
607
|
-
current =
|
608
|
-
stored =
|
634
|
+
migration_context = pool.migration_context
|
635
|
+
current = migration_context.current_environment
|
636
|
+
stored = migration_context.last_stored_environment
|
609
637
|
|
610
|
-
if
|
638
|
+
if migration_context.protected_environment?
|
611
639
|
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
612
640
|
end
|
613
641
|
|
@@ -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)
|
@@ -13,6 +13,7 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
def after_teardown # :nodoc:
|
15
15
|
super
|
16
|
+
ensure
|
16
17
|
teardown_fixtures
|
17
18
|
end
|
18
19
|
|
@@ -52,20 +53,6 @@ module ActiveRecord
|
|
52
53
|
self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
|
53
54
|
end
|
54
55
|
|
55
|
-
def fixture_path # :nodoc:
|
56
|
-
ActiveRecord.deprecator.warn(<<~WARNING)
|
57
|
-
TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
|
58
|
-
If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
|
59
|
-
the first path.
|
60
|
-
WARNING
|
61
|
-
fixture_paths.first
|
62
|
-
end
|
63
|
-
|
64
|
-
def fixture_path=(path) # :nodoc:
|
65
|
-
ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
|
66
|
-
self.fixture_paths = Array(path)
|
67
|
-
end
|
68
|
-
|
69
56
|
def fixtures(*fixture_set_names)
|
70
57
|
if fixture_set_names.first == :all
|
71
58
|
raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
|
@@ -78,7 +65,7 @@ module ActiveRecord
|
|
78
65
|
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
|
79
66
|
end
|
80
67
|
|
81
|
-
self.fixture_table_names
|
68
|
+
self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
|
82
69
|
setup_fixture_accessors(fixture_set_names)
|
83
70
|
end
|
84
71
|
|
@@ -109,45 +96,68 @@ module ActiveRecord
|
|
109
96
|
end
|
110
97
|
end
|
111
98
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
the first path.
|
117
|
-
WARNING
|
118
|
-
fixture_paths.first
|
119
|
-
end
|
120
|
-
|
121
|
-
def run_in_transaction?
|
122
|
-
use_transactional_tests &&
|
123
|
-
!self.class.uses_transaction?(name)
|
124
|
-
end
|
125
|
-
|
126
|
-
def setup_fixtures(config = ActiveRecord::Base)
|
127
|
-
if pre_loaded_fixtures && !use_transactional_tests
|
128
|
-
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
|
99
|
+
private
|
100
|
+
def run_in_transaction?
|
101
|
+
use_transactional_tests &&
|
102
|
+
!self.class.uses_transaction?(name)
|
129
103
|
end
|
130
104
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
105
|
+
def setup_fixtures(config = ActiveRecord::Base)
|
106
|
+
if pre_loaded_fixtures && !use_transactional_tests
|
107
|
+
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
|
108
|
+
end
|
136
109
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
110
|
+
@fixture_cache = {}
|
111
|
+
@fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
|
112
|
+
@fixture_connection_pools = []
|
113
|
+
@@already_loaded_fixtures ||= {}
|
114
|
+
@connection_subscriber = nil
|
115
|
+
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
116
|
+
|
117
|
+
if run_in_transaction?
|
118
|
+
# Load fixtures once and begin transaction.
|
119
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
|
120
|
+
unless @loaded_fixtures
|
121
|
+
@@already_loaded_fixtures.clear
|
122
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
|
123
|
+
end
|
124
|
+
|
125
|
+
setup_transactional_fixtures
|
141
126
|
else
|
127
|
+
# Load fixtures for every test.
|
128
|
+
ActiveRecord::FixtureSet.reset_cache
|
129
|
+
invalidate_already_loaded_fixtures
|
142
130
|
@loaded_fixtures = load_fixtures(config)
|
143
|
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
144
131
|
end
|
145
132
|
|
133
|
+
# Instantiate fixtures for every test if requested.
|
134
|
+
instantiate_fixtures if use_instantiated_fixtures
|
135
|
+
end
|
136
|
+
|
137
|
+
def teardown_fixtures
|
138
|
+
# Rollback changes if a transaction is active.
|
139
|
+
if run_in_transaction?
|
140
|
+
teardown_transactional_fixtures
|
141
|
+
else
|
142
|
+
ActiveRecord::FixtureSet.reset_cache
|
143
|
+
invalidate_already_loaded_fixtures
|
144
|
+
end
|
145
|
+
|
146
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
147
|
+
end
|
148
|
+
|
149
|
+
def invalidate_already_loaded_fixtures
|
150
|
+
@@already_loaded_fixtures.clear
|
151
|
+
end
|
152
|
+
|
153
|
+
def setup_transactional_fixtures
|
154
|
+
setup_shared_connection_pool
|
155
|
+
|
146
156
|
# Begin transactions for connections already established
|
147
|
-
@
|
148
|
-
@
|
149
|
-
|
150
|
-
|
157
|
+
@fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
|
158
|
+
@fixture_connection_pools.each do |pool|
|
159
|
+
pool.pin_connection!(lock_threads)
|
160
|
+
pool.lease_connection
|
151
161
|
end
|
152
162
|
|
153
163
|
# When connections are established in the future, begin a transaction too
|
@@ -156,59 +166,31 @@ module ActiveRecord
|
|
156
166
|
shard = payload[:shard] if payload.key?(:shard)
|
157
167
|
|
158
168
|
if connection_name
|
159
|
-
|
160
|
-
|
161
|
-
rescue ConnectionNotEstablished
|
162
|
-
connection = nil
|
163
|
-
end
|
164
|
-
|
165
|
-
if connection
|
169
|
+
pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
170
|
+
if pool
|
166
171
|
setup_shared_connection_pool
|
167
172
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
@
|
173
|
+
unless @fixture_connection_pools.include?(pool)
|
174
|
+
pool.pin_connection!(lock_threads)
|
175
|
+
pool.lease_connection
|
176
|
+
@fixture_connection_pools << pool
|
172
177
|
end
|
173
178
|
end
|
174
179
|
end
|
175
180
|
end
|
176
|
-
|
177
|
-
# Load fixtures for every test.
|
178
|
-
else
|
179
|
-
ActiveRecord::FixtureSet.reset_cache
|
180
|
-
@@already_loaded_fixtures[self.class] = nil
|
181
|
-
@loaded_fixtures = load_fixtures(config)
|
182
181
|
end
|
183
182
|
|
184
|
-
|
185
|
-
instantiate_fixtures if use_instantiated_fixtures
|
186
|
-
end
|
187
|
-
|
188
|
-
def teardown_fixtures
|
189
|
-
# Rollback changes if a transaction is active.
|
190
|
-
if run_in_transaction?
|
183
|
+
def teardown_transactional_fixtures
|
191
184
|
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
192
|
-
@
|
193
|
-
|
194
|
-
|
185
|
+
unless @fixture_connection_pools.map(&:unpin_connection!).all?
|
186
|
+
# Something caused the transaction to be committed or rolled back
|
187
|
+
# We can no longer trust the database is in a clean state.
|
188
|
+
@@already_loaded_fixtures.clear
|
195
189
|
end
|
196
|
-
@
|
190
|
+
@fixture_connection_pools.clear
|
197
191
|
teardown_shared_connection_pool
|
198
|
-
else
|
199
|
-
ActiveRecord::FixtureSet.reset_cache
|
200
192
|
end
|
201
193
|
|
202
|
-
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
203
|
-
end
|
204
|
-
|
205
|
-
def enlist_fixture_connections
|
206
|
-
setup_shared_connection_pool
|
207
|
-
|
208
|
-
ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
|
209
|
-
end
|
210
|
-
|
211
|
-
private
|
212
194
|
# Shares the writing connection pool with connections on
|
213
195
|
# other handlers.
|
214
196
|
#
|
@@ -271,22 +253,31 @@ module ActiveRecord
|
|
271
253
|
use_instantiated_fixtures != :no_instances
|
272
254
|
end
|
273
255
|
|
274
|
-
def method_missing(
|
275
|
-
if
|
276
|
-
|
256
|
+
def method_missing(method, ...)
|
257
|
+
if fixture_sets.key?(method.name)
|
258
|
+
_active_record_fixture(method, ...)
|
277
259
|
else
|
278
260
|
super
|
279
261
|
end
|
280
262
|
end
|
281
263
|
|
282
|
-
def respond_to_missing?(
|
283
|
-
if include_private && fixture_sets.key?(name
|
264
|
+
def respond_to_missing?(method, include_private = false)
|
265
|
+
if include_private && fixture_sets.key?(method.name)
|
284
266
|
true
|
285
267
|
else
|
286
268
|
super
|
287
269
|
end
|
288
270
|
end
|
289
271
|
|
272
|
+
def _active_record_fixture(fixture_set_name, *fixture_names)
|
273
|
+
if fs_name = fixture_sets[fixture_set_name.name]
|
274
|
+
access_fixture(fs_name, *fixture_names)
|
275
|
+
else
|
276
|
+
raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
alias_method :fixture, :_active_record_fixture
|
280
|
+
|
290
281
|
def access_fixture(fs_name, *fixture_names)
|
291
282
|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
292
283
|
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
|
@@ -54,8 +54,10 @@ module ActiveRecord
|
|
54
54
|
|
55
55
|
module ClassMethods # :nodoc:
|
56
56
|
def touch_attributes_with_time(*names, time: nil)
|
57
|
+
names = names.map(&:to_s)
|
58
|
+
names = names.map { |name| attribute_aliases[name] || name }
|
57
59
|
attribute_names = timestamp_attributes_for_update_in_model
|
58
|
-
attribute_names |= names
|
60
|
+
attribute_names |= names
|
59
61
|
attribute_names.index_with(time || current_time_from_proper_timezone)
|
60
62
|
end
|
61
63
|
|
@@ -75,7 +77,7 @@ module ActiveRecord
|
|
75
77
|
end
|
76
78
|
|
77
79
|
def current_time_from_proper_timezone
|
78
|
-
|
80
|
+
with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now }
|
79
81
|
end
|
80
82
|
|
81
83
|
protected
|