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

@@ -1,6 +1,4 @@
require 'yaml'
require 'erb'
require 'tmpdir'
require 'uri'
# Multi-database test runner
# ==========================
@@ -25,9 +23,10 @@ require 'tmpdir'
#
# A single-database setup is unchanged: every task behaves exactly as before.
#
# The mechanism uses RAILS_DATABASE_YML — an env var read by
# config/application.rb(.dist) to override Rails' database config path before
# initialisation, giving each subprocess a clean, isolated database config.
# Database configuration is read via Rails.application.config.database_configuration
# so ERB interpolation and all Rails path overrides are respected automatically.
# 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
ADAPTER_GEMS = {
@@ -55,19 +54,8 @@ module MultiDbTests
class << self
# Returns {name => config_hash} for every key starting with "test".
def test_configs
@test_configs ||= begin
db_file = Rails.root.join('config', 'database.yml')
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
@test_configs ||= Rails.application.config.database_configuration
.select { |k, v| k.to_s.start_with?('test') && v.is_a?(Hash) }
end
# Run rails +task_name+ for every configured test database.
@@ -89,16 +77,12 @@ module MultiDbTests
next
end
Dir.mktmpdir('rails_test_') do |tmpdir|
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 }
env = { 'DATABASE_URL' => config_to_url(config) }
if system(env, 'bundle exec rails db:test:prepare')
results[db_name] = system(env, 'rails', task_name) ? :pass : :fail
else
results[db_name] = :prepare_failed
end
if system(env, 'bundle exec rails db:test:prepare')
results[db_name] = system(env, 'rails', task_name) ? :pass : :fail
else
results[db_name] = :prepare_failed
end
end
@@ -110,6 +94,28 @@ module MultiDbTests
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)
require ADAPTER_GEMS.fetch(adapter, adapter)
true