activerecord 7.1.4.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 +515 -2397
- 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 +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 +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +1 -1
- 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 +33 -16
- 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 +4 -16
- 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/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- 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 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -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 +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- 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 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- 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 +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 +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- 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 +52 -36
- 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 +15 -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 +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- 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/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 +8 -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 +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 +28 -67
- 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 +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +48 -57
- 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 +90 -35
- 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.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- 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/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 +76 -59
- 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 +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- 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 +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/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -15
|
@@ -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
|
|
@@ -192,17 +192,9 @@ module ActiveRecord
|
|
|
192
192
|
|
|
193
193
|
seed = true
|
|
194
194
|
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
195
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
db_configs.each do |db_config|
|
|
201
|
-
with_temporary_pool(db_config) do
|
|
202
|
-
migrate(version)
|
|
203
|
-
dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
|
|
204
|
-
end
|
|
205
|
-
end
|
|
196
|
+
migrate
|
|
197
|
+
dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
|
|
206
198
|
end
|
|
207
199
|
end
|
|
208
200
|
|
|
@@ -248,7 +240,7 @@ module ActiveRecord
|
|
|
248
240
|
|
|
249
241
|
check_target_version
|
|
250
242
|
|
|
251
|
-
|
|
243
|
+
migration_connection_pool.migration_context.migrate(target_version) do |migration|
|
|
252
244
|
if version.blank?
|
|
253
245
|
scope.blank? || scope == migration.scope
|
|
254
246
|
else
|
|
@@ -258,17 +250,17 @@ module ActiveRecord
|
|
|
258
250
|
Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
|
|
259
251
|
end
|
|
260
252
|
|
|
261
|
-
|
|
253
|
+
migration_connection_pool.schema_cache.clear!
|
|
262
254
|
ensure
|
|
263
255
|
Migration.verbose = verbose_was
|
|
264
256
|
end
|
|
265
257
|
|
|
266
|
-
def db_configs_with_versions
|
|
258
|
+
def db_configs_with_versions # :nodoc:
|
|
267
259
|
db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
|
|
268
260
|
|
|
269
|
-
|
|
270
|
-
db_config =
|
|
271
|
-
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
|
|
272
264
|
target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
|
|
273
265
|
|
|
274
266
|
versions_to_run.each do |version|
|
|
@@ -281,15 +273,15 @@ module ActiveRecord
|
|
|
281
273
|
end
|
|
282
274
|
|
|
283
275
|
def migrate_status
|
|
284
|
-
unless
|
|
276
|
+
unless migration_connection_pool.schema_migration.table_exists?
|
|
285
277
|
Kernel.abort "Schema migrations table does not exist yet."
|
|
286
278
|
end
|
|
287
279
|
|
|
288
280
|
# output
|
|
289
|
-
puts "\ndatabase: #{
|
|
281
|
+
puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
|
|
290
282
|
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
|
|
291
283
|
puts "-" * 50
|
|
292
|
-
|
|
284
|
+
migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
|
|
293
285
|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
|
|
294
286
|
end
|
|
295
287
|
puts
|
|
@@ -370,7 +362,7 @@ module ActiveRecord
|
|
|
370
362
|
raise ArgumentError, "unknown format #{format.inspect}"
|
|
371
363
|
end
|
|
372
364
|
|
|
373
|
-
|
|
365
|
+
migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
|
|
374
366
|
ensure
|
|
375
367
|
Migration.verbose = verbose_was
|
|
376
368
|
end
|
|
@@ -382,11 +374,12 @@ module ActiveRecord
|
|
|
382
374
|
|
|
383
375
|
return true unless file && File.exist?(file)
|
|
384
376
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
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?
|
|
388
381
|
|
|
389
|
-
|
|
382
|
+
internal_metadata[:schema_sha1] == schema_sha1(file)
|
|
390
383
|
end
|
|
391
384
|
end
|
|
392
385
|
|
|
@@ -397,7 +390,7 @@ module ActiveRecord
|
|
|
397
390
|
|
|
398
391
|
with_temporary_pool(db_config, clobber: true) do
|
|
399
392
|
if schema_up_to_date?(db_config, format, file)
|
|
400
|
-
truncate_tables(db_config)
|
|
393
|
+
truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
|
|
401
394
|
else
|
|
402
395
|
purge(db_config)
|
|
403
396
|
load_schema(db_config, format, file)
|
|
@@ -419,11 +412,11 @@ module ActiveRecord
|
|
|
419
412
|
case format
|
|
420
413
|
when :ruby
|
|
421
414
|
File.open(filename, "w:utf-8") do |file|
|
|
422
|
-
ActiveRecord::SchemaDumper.dump(
|
|
415
|
+
ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
|
|
423
416
|
end
|
|
424
417
|
when :sql
|
|
425
418
|
structure_dump(db_config, filename)
|
|
426
|
-
if
|
|
419
|
+
if migration_connection_pool.schema_migration.table_exists?
|
|
427
420
|
File.open(filename, "a") do |f|
|
|
428
421
|
f.puts migration_connection.dump_schema_information
|
|
429
422
|
f.print "\n"
|
|
@@ -445,14 +438,26 @@ module ActiveRecord
|
|
|
445
438
|
end
|
|
446
439
|
end
|
|
447
440
|
|
|
448
|
-
def cache_dump_filename(
|
|
449
|
-
|
|
450
|
-
|
|
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)
|
|
451
447
|
else
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
452
|
+
|
|
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
|
|
454
458
|
|
|
455
|
-
|
|
459
|
+
schema_cache_path || schema_cache_env || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
|
|
460
|
+
end
|
|
456
461
|
end
|
|
457
462
|
|
|
458
463
|
def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
|
|
@@ -484,29 +489,29 @@ module ActiveRecord
|
|
|
484
489
|
# Dumps the schema cache in YAML format for the connection into the file
|
|
485
490
|
#
|
|
486
491
|
# ==== Examples
|
|
487
|
-
# ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.
|
|
488
|
-
def dump_schema_cache(
|
|
489
|
-
|
|
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)
|
|
490
495
|
end
|
|
491
496
|
|
|
492
497
|
def clear_schema_cache(filename)
|
|
493
498
|
FileUtils.rm_f filename, verbose: false
|
|
494
499
|
end
|
|
495
500
|
|
|
496
|
-
def
|
|
501
|
+
def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
|
|
497
502
|
if name
|
|
498
503
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
|
|
499
|
-
|
|
504
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
|
500
505
|
else
|
|
501
506
|
ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
|
|
502
|
-
|
|
507
|
+
with_temporary_pool(db_config, clobber: clobber, &block)
|
|
503
508
|
end
|
|
504
509
|
end
|
|
505
510
|
end
|
|
506
511
|
|
|
507
|
-
def with_temporary_connection(db_config, clobber: false) # :nodoc:
|
|
512
|
+
def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
|
|
508
513
|
with_temporary_pool(db_config, clobber: clobber) do |pool|
|
|
509
|
-
|
|
514
|
+
pool.with_connection(&block)
|
|
510
515
|
end
|
|
511
516
|
end
|
|
512
517
|
|
|
@@ -515,10 +520,25 @@ module ActiveRecord
|
|
|
515
520
|
end
|
|
516
521
|
|
|
517
522
|
def migration_connection # :nodoc:
|
|
518
|
-
migration_class.
|
|
523
|
+
migration_class.lease_connection
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def migration_connection_pool # :nodoc:
|
|
527
|
+
migration_class.connection_pool
|
|
519
528
|
end
|
|
520
529
|
|
|
521
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
|
+
|
|
522
542
|
def with_temporary_pool(db_config, clobber: false)
|
|
523
543
|
original_db_config = migration_class.connection_db_config
|
|
524
544
|
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
|
|
@@ -560,7 +580,10 @@ module ActiveRecord
|
|
|
560
580
|
end
|
|
561
581
|
|
|
562
582
|
def each_current_configuration(environment, name = nil)
|
|
563
|
-
|
|
583
|
+
environments = [environment]
|
|
584
|
+
environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"]
|
|
585
|
+
|
|
586
|
+
environments.each do |env|
|
|
564
587
|
configs_for(env_name: env).each do |db_config|
|
|
565
588
|
next if name && name != db_config.name
|
|
566
589
|
|
|
@@ -569,12 +592,6 @@ module ActiveRecord
|
|
|
569
592
|
end
|
|
570
593
|
end
|
|
571
594
|
|
|
572
|
-
def each_current_environment(environment, &block)
|
|
573
|
-
environments = [environment]
|
|
574
|
-
environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"]
|
|
575
|
-
environments.each(&block)
|
|
576
|
-
end
|
|
577
|
-
|
|
578
595
|
def each_local_configuration
|
|
579
596
|
configs_for.each do |db_config|
|
|
580
597
|
next unless db_config.database
|
|
@@ -614,11 +631,11 @@ module ActiveRecord
|
|
|
614
631
|
|
|
615
632
|
def check_current_protected_environment!(db_config)
|
|
616
633
|
with_temporary_pool(db_config) do |pool|
|
|
617
|
-
|
|
618
|
-
current =
|
|
619
|
-
stored =
|
|
634
|
+
migration_context = pool.migration_context
|
|
635
|
+
current = migration_context.current_environment
|
|
636
|
+
stored = migration_context.last_stored_environment
|
|
620
637
|
|
|
621
|
-
if
|
|
638
|
+
if migration_context.protected_environment?
|
|
622
639
|
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
|
|
623
640
|
end
|
|
624
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)
|
|
@@ -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,68 @@ module ActiveRecord
|
|
|
110
96
|
end
|
|
111
97
|
end
|
|
112
98
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
the first path.
|
|
118
|
-
WARNING
|
|
119
|
-
fixture_paths.first
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def run_in_transaction?
|
|
123
|
-
use_transactional_tests &&
|
|
124
|
-
!self.class.uses_transaction?(name)
|
|
125
|
-
end
|
|
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"
|
|
99
|
+
private
|
|
100
|
+
def run_in_transaction?
|
|
101
|
+
use_transactional_tests &&
|
|
102
|
+
!self.class.uses_transaction?(name)
|
|
130
103
|
end
|
|
131
104
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@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
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
126
|
else
|
|
127
|
+
# Load fixtures for every test.
|
|
128
|
+
ActiveRecord::FixtureSet.reset_cache
|
|
129
|
+
invalidate_already_loaded_fixtures
|
|
143
130
|
@loaded_fixtures = load_fixtures(config)
|
|
144
|
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
|
145
131
|
end
|
|
146
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
|
+
|
|
147
156
|
# Begin transactions for connections already established
|
|
148
|
-
@
|
|
149
|
-
@
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
152
161
|
end
|
|
153
162
|
|
|
154
163
|
# When connections are established in the future, begin a transaction too
|
|
@@ -157,59 +166,31 @@ module ActiveRecord
|
|
|
157
166
|
shard = payload[:shard] if payload.key?(:shard)
|
|
158
167
|
|
|
159
168
|
if connection_name
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
rescue ConnectionNotEstablished
|
|
163
|
-
connection = nil
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
if connection
|
|
169
|
+
pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
|
170
|
+
if pool
|
|
167
171
|
setup_shared_connection_pool
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@
|
|
173
|
+
unless @fixture_connection_pools.include?(pool)
|
|
174
|
+
pool.pin_connection!(lock_threads)
|
|
175
|
+
pool.lease_connection
|
|
176
|
+
@fixture_connection_pools << pool
|
|
173
177
|
end
|
|
174
178
|
end
|
|
175
179
|
end
|
|
176
180
|
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
181
|
end
|
|
184
182
|
|
|
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?
|
|
183
|
+
def teardown_transactional_fixtures
|
|
192
184
|
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
|
193
|
-
@
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
196
189
|
end
|
|
197
|
-
@
|
|
190
|
+
@fixture_connection_pools.clear
|
|
198
191
|
teardown_shared_connection_pool
|
|
199
|
-
else
|
|
200
|
-
ActiveRecord::FixtureSet.reset_cache
|
|
201
192
|
end
|
|
202
193
|
|
|
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
194
|
# Shares the writing connection pool with connections on
|
|
214
195
|
# other handlers.
|
|
215
196
|
#
|
|
@@ -272,22 +253,31 @@ module ActiveRecord
|
|
|
272
253
|
use_instantiated_fixtures != :no_instances
|
|
273
254
|
end
|
|
274
255
|
|
|
275
|
-
def method_missing(
|
|
276
|
-
if
|
|
277
|
-
|
|
256
|
+
def method_missing(method, ...)
|
|
257
|
+
if fixture_sets.key?(method.name)
|
|
258
|
+
_active_record_fixture(method, ...)
|
|
278
259
|
else
|
|
279
260
|
super
|
|
280
261
|
end
|
|
281
262
|
end
|
|
282
263
|
|
|
283
|
-
def respond_to_missing?(
|
|
284
|
-
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)
|
|
285
266
|
true
|
|
286
267
|
else
|
|
287
268
|
super
|
|
288
269
|
end
|
|
289
270
|
end
|
|
290
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
|
+
|
|
291
281
|
def access_fixture(fs_name, *fixture_names)
|
|
292
282
|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
|
293
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
|