activerecord 7.1.4.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 +643 -2274
- 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 +30 -27
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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 +11 -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/cidr.rb +1 -1
- 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 +17 -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 +86 -38
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- 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/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- 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/marshalling.rb +4 -1
- 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 +32 -68
- data/lib/active_record/nested_attributes.rb +24 -5
- 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 +42 -57
- 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 +81 -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 +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 +18 -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:
|
@@ -175,11 +175,12 @@ module ActiveRecord
|
|
175
175
|
|
176
176
|
def prepare_all
|
177
177
|
seed = false
|
178
|
+
dump_db_configs = []
|
178
179
|
|
179
180
|
each_current_configuration(env) do |db_config|
|
180
181
|
with_temporary_pool(db_config) do
|
181
182
|
begin
|
182
|
-
database_initialized =
|
183
|
+
database_initialized = migration_connection_pool.schema_migration.table_exists?
|
183
184
|
rescue ActiveRecord::NoDatabaseError
|
184
185
|
create(db_config)
|
185
186
|
retry
|
@@ -197,15 +198,25 @@ module ActiveRecord
|
|
197
198
|
|
198
199
|
each_current_environment(env) do |environment|
|
199
200
|
db_configs_with_versions(environment).sort.each do |version, db_configs|
|
201
|
+
dump_db_configs |= db_configs
|
202
|
+
|
200
203
|
db_configs.each do |db_config|
|
201
204
|
with_temporary_pool(db_config) do
|
202
205
|
migrate(version)
|
203
|
-
dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
|
204
206
|
end
|
205
207
|
end
|
206
208
|
end
|
207
209
|
end
|
208
210
|
|
211
|
+
# Dump schema for databases that were migrated.
|
212
|
+
if ActiveRecord.dump_schema_after_migration
|
213
|
+
dump_db_configs.each do |db_config|
|
214
|
+
with_temporary_pool(db_config) do
|
215
|
+
dump_schema(db_config)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
209
220
|
load_seed if seed
|
210
221
|
end
|
211
222
|
|
@@ -248,7 +259,7 @@ module ActiveRecord
|
|
248
259
|
|
249
260
|
check_target_version
|
250
261
|
|
251
|
-
|
262
|
+
migration_connection_pool.migration_context.migrate(target_version) do |migration|
|
252
263
|
if version.blank?
|
253
264
|
scope.blank? || scope == migration.scope
|
254
265
|
else
|
@@ -258,7 +269,7 @@ module ActiveRecord
|
|
258
269
|
Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
|
259
270
|
end
|
260
271
|
|
261
|
-
|
272
|
+
migration_connection_pool.schema_cache.clear!
|
262
273
|
ensure
|
263
274
|
Migration.verbose = verbose_was
|
264
275
|
end
|
@@ -266,9 +277,9 @@ module ActiveRecord
|
|
266
277
|
def db_configs_with_versions(environment = env) # :nodoc:
|
267
278
|
db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
|
268
279
|
|
269
|
-
|
270
|
-
db_config =
|
271
|
-
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
|
272
283
|
target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
|
273
284
|
|
274
285
|
versions_to_run.each do |version|
|
@@ -281,15 +292,15 @@ module ActiveRecord
|
|
281
292
|
end
|
282
293
|
|
283
294
|
def migrate_status
|
284
|
-
unless
|
295
|
+
unless migration_connection_pool.schema_migration.table_exists?
|
285
296
|
Kernel.abort "Schema migrations table does not exist yet."
|
286
297
|
end
|
287
298
|
|
288
299
|
# output
|
289
|
-
puts "\ndatabase: #{
|
300
|
+
puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
|
290
301
|
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
|
291
302
|
puts "-" * 50
|
292
|
-
|
303
|
+
migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
|
293
304
|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
|
294
305
|
end
|
295
306
|
puts
|
@@ -370,7 +381,7 @@ module ActiveRecord
|
|
370
381
|
raise ArgumentError, "unknown format #{format.inspect}"
|
371
382
|
end
|
372
383
|
|
373
|
-
|
384
|
+
migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
|
374
385
|
ensure
|
375
386
|
Migration.verbose = verbose_was
|
376
387
|
end
|
@@ -382,11 +393,12 @@ module ActiveRecord
|
|
382
393
|
|
383
394
|
return true unless file && File.exist?(file)
|
384
395
|
|
385
|
-
|
386
|
-
|
387
|
-
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?
|
388
400
|
|
389
|
-
|
401
|
+
internal_metadata[:schema_sha1] == schema_sha1(file)
|
390
402
|
end
|
391
403
|
end
|
392
404
|
|
@@ -397,7 +409,7 @@ module ActiveRecord
|
|
397
409
|
|
398
410
|
with_temporary_pool(db_config, clobber: true) do
|
399
411
|
if schema_up_to_date?(db_config, format, file)
|
400
|
-
truncate_tables(db_config)
|
412
|
+
truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
|
401
413
|
else
|
402
414
|
purge(db_config)
|
403
415
|
load_schema(db_config, format, file)
|
@@ -419,11 +431,11 @@ module ActiveRecord
|
|
419
431
|
case format
|
420
432
|
when :ruby
|
421
433
|
File.open(filename, "w:utf-8") do |file|
|
422
|
-
ActiveRecord::SchemaDumper.dump(
|
434
|
+
ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
|
423
435
|
end
|
424
436
|
when :sql
|
425
437
|
structure_dump(db_config, filename)
|
426
|
-
if
|
438
|
+
if migration_connection_pool.schema_migration.table_exists?
|
427
439
|
File.open(filename, "a") do |f|
|
428
440
|
f.puts migration_connection.dump_schema_information
|
429
441
|
f.print "\n"
|
@@ -445,14 +457,26 @@ module ActiveRecord
|
|
445
457
|
end
|
446
458
|
end
|
447
459
|
|
448
|
-
def cache_dump_filename(
|
449
|
-
|
450
|
-
|
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)
|
451
466
|
else
|
452
|
-
|
453
|
-
|
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
|
454
471
|
|
455
|
-
|
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
|
456
480
|
end
|
457
481
|
|
458
482
|
def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
|
@@ -484,29 +508,29 @@ module ActiveRecord
|
|
484
508
|
# Dumps the schema cache in YAML format for the connection into the file
|
485
509
|
#
|
486
510
|
# ==== Examples
|
487
|
-
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.
|
488
|
-
def dump_schema_cache(
|
489
|
-
|
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)
|
490
514
|
end
|
491
515
|
|
492
516
|
def clear_schema_cache(filename)
|
493
517
|
FileUtils.rm_f filename, verbose: false
|
494
518
|
end
|
495
519
|
|
496
|
-
def
|
520
|
+
def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
|
497
521
|
if name
|
498
522
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
|
499
|
-
|
523
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
500
524
|
else
|
501
525
|
ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
|
502
|
-
|
526
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
503
527
|
end
|
504
528
|
end
|
505
529
|
end
|
506
530
|
|
507
|
-
def with_temporary_connection(db_config, clobber: false) # :nodoc:
|
531
|
+
def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
|
508
532
|
with_temporary_pool(db_config, clobber: clobber) do |pool|
|
509
|
-
|
533
|
+
pool.with_connection(&block)
|
510
534
|
end
|
511
535
|
end
|
512
536
|
|
@@ -515,10 +539,25 @@ module ActiveRecord
|
|
515
539
|
end
|
516
540
|
|
517
541
|
def migration_connection # :nodoc:
|
518
|
-
migration_class.
|
542
|
+
migration_class.lease_connection
|
543
|
+
end
|
544
|
+
|
545
|
+
def migration_connection_pool # :nodoc:
|
546
|
+
migration_class.connection_pool
|
519
547
|
end
|
520
548
|
|
521
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
|
+
|
522
561
|
def with_temporary_pool(db_config, clobber: false)
|
523
562
|
original_db_config = migration_class.connection_db_config
|
524
563
|
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
@@ -614,11 +653,11 @@ module ActiveRecord
|
|
614
653
|
|
615
654
|
def check_current_protected_environment!(db_config)
|
616
655
|
with_temporary_pool(db_config) do |pool|
|
617
|
-
|
618
|
-
current =
|
619
|
-
stored =
|
656
|
+
migration_context = pool.migration_context
|
657
|
+
current = migration_context.current_environment
|
658
|
+
stored = migration_context.last_stored_environment
|
620
659
|
|
621
|
-
if
|
660
|
+
if migration_context.protected_environment?
|
622
661
|
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
623
662
|
end
|
624
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
|