Use DATABASE_URL instead of temp database.yml for multi-db tests

Replace the RAILS_DATABASE_YML / Dir.mktmpdir approach with DATABASE_URL:
- Read database config via Rails.application.config.database_configuration
  instead of manually parsing YAML+ERB
- Build a DATABASE_URL from each test config and pass it to subprocesses;
  Rails merges it on top of the test: entry — no temp files needed
- Remove non_test_configs, Dir.mktmpdir, and the require yaml/erb/tmpdir
- Remove RAILS_DATABASE_YML override from config/application.rb(.dist)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 11:47:35 +00:00
parent 3e0f27b357
commit badb4b336c
2 changed files with 34 additions and 32 deletions

View File

@@ -20,10 +20,6 @@ Bundler.require(*Rails.groups)
module FixinMe module FixinMe
class Application < Rails::Application class Application < Rails::Application
# Allow RAILS_DATABASE_YML to override the database config file path.
# Used by the multi-database test runner (lib/tasks/test_databases.rake).
config.paths['config/database'] = [ENV['RAILS_DATABASE_YML']] if ENV['RAILS_DATABASE_YML']
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0 config.load_defaults 7.0

View File

@@ -1,6 +1,4 @@
require 'yaml' require 'uri'
require 'erb'
require 'tmpdir'
# Multi-database test runner # Multi-database test runner
# ========================== # ==========================
@@ -25,9 +23,10 @@ require 'tmpdir'
# #
# A single-database setup is unchanged: every task behaves exactly as before. # A single-database setup is unchanged: every task behaves exactly as before.
# #
# The mechanism uses RAILS_DATABASE_YML — an env var read by # Database configuration is read via Rails.application.config.database_configuration
# config/application.rb(.dist) to override Rails' database config path before # so ERB interpolation and all Rails path overrides are respected automatically.
# initialisation, giving each subprocess a clean, isolated database config. # Each subprocess receives DATABASE_URL built from the selected config, which
# Rails merges on top of the test: entry from database.yml — no temporary files needed.
module MultiDbTests module MultiDbTests
ADAPTER_GEMS = { ADAPTER_GEMS = {
@@ -55,19 +54,8 @@ module MultiDbTests
class << self class << self
# Returns {name => config_hash} for every key starting with "test". # Returns {name => config_hash} for every key starting with "test".
def test_configs def test_configs
@test_configs ||= begin @test_configs ||= Rails.application.config.database_configuration
db_file = Rails.root.join('config', 'database.yml') .select { |k, v| k.to_s.start_with?('test') && v.is_a?(Hash) }
all = YAML.safe_load(ERB.new(db_file.read).result, aliases: true) || {}
all.select { |k, v| k.to_s.start_with?('test') && v.is_a?(Hash) }
end
end
def non_test_configs
@non_test_configs ||= begin
db_file = Rails.root.join('config', 'database.yml')
all = YAML.safe_load(ERB.new(db_file.read).result, aliases: true) || {}
all.reject { |k, _| k.to_s.start_with?('test') }
end
end end
# Run rails +task_name+ for every configured test database. # Run rails +task_name+ for every configured test database.
@@ -89,10 +77,7 @@ module MultiDbTests
next next
end end
Dir.mktmpdir('rails_test_') do |tmpdir| env = { 'DATABASE_URL' => config_to_url(config) }
tmp_yml = File.join(tmpdir, 'database.yml')
File.write(tmp_yml, non_test_configs.merge('test' => config).to_yaml)
env = { 'RAILS_DATABASE_YML' => tmp_yml }
if system(env, 'bundle exec rails db:test:prepare') if system(env, 'bundle exec rails db:test:prepare')
results[db_name] = system(env, 'rails', task_name) ? :pass : :fail results[db_name] = system(env, 'rails', task_name) ? :pass : :fail
@@ -100,7 +85,6 @@ module MultiDbTests
results[db_name] = :prepare_failed results[db_name] = :prepare_failed
end end
end end
end
print_summary(cfgs, results) print_summary(cfgs, results)
@@ -110,6 +94,28 @@ module MultiDbTests
private private
# Build a DATABASE_URL string from a database.yml config hash.
# Rails merges DATABASE_URL on top of the test: entry, so adapter and
# connection details from the URL take precedence over the YAML file.
def config_to_url(config)
adapter = config['adapter'].to_s
database = config['database'].to_s
return "sqlite3:#{database}" if adapter == 'sqlite3'
user = config['username']
pass = config['password']
host = config['host'] || 'localhost'
port = config['port']
socket = config['socket']
userinfo = user ? "#{URI.encode_www_form_component(user)}:#{URI.encode_www_form_component(pass.to_s)}@" : ''
hostport = port ? "#{host}:#{port}" : host
url = "#{adapter}://#{userinfo}#{hostport}/#{database}"
url += "?socket=#{URI.encode_www_form_component(socket)}" if socket
url
end
def adapter_available?(adapter) def adapter_available?(adapter)
require ADAPTER_GEMS.fetch(adapter, adapter) require ADAPTER_GEMS.fetch(adapter, adapter)
true true