Skip to content

Commit a744d47

Browse files
authored
Merge pull request rails#46097 from Shopify/fix-multi-db-check-pending
Multi-database: better support pending migration checks
2 parents 56ee229 + 6793482 commit a744d47

File tree

2 files changed

+94
-14
lines changed

2 files changed

+94
-14
lines changed

activerecord/lib/active_record/migration.rb

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ def call(env)
601601
@mutex.synchronize do
602602
@watcher ||= build_watcher do
603603
@needs_check = true
604-
ActiveRecord::Migration.check_pending!(connection)
604+
ActiveRecord::Migration.check_pending_migrations
605605
@needs_check = false
606606
end
607607

@@ -617,13 +617,11 @@ def call(env)
617617

618618
private
619619
def build_watcher(&block)
620-
paths = Array(connection.migration_context.migrations_paths)
620+
current_environment = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
621+
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: current_environment)
622+
paths = all_configs.flat_map { |config| config.migrations_paths || Migrator.migrations_paths }.uniq
621623
@file_watcher.new([], paths.index_with(["rb"]), &block)
622624
end
623-
624-
def connection
625-
ActiveRecord::Base.connection
626-
end
627625
end
628626

629627
class << self
@@ -641,7 +639,7 @@ def check_pending!(connection = Base.connection)
641639

642640
def load_schema_if_pending!
643641
current_db_config = Base.connection_db_config
644-
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
642+
all_configs = db_configs_in_current_env
645643

646644
needs_update = !all_configs.all? do |db_config|
647645
Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord.schema_format)
@@ -659,7 +657,8 @@ def load_schema_if_pending!
659657
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
660658
Base.establish_connection(current_db_config)
661659

662-
check_pending!
660+
# Ensure all migrations have succeeded
661+
check_pending_migrations(db_configs: all_configs)
663662
end
664663

665664
def maintain_test_schema! # :nodoc:
@@ -684,6 +683,22 @@ def migrate(direction)
684683
def disable_ddl_transaction!
685684
@disable_ddl_transaction = true
686685
end
686+
687+
def check_pending_migrations(db_configs: db_configs_in_current_env) # :nodoc:
688+
prev_db_config = Base.connection_db_config
689+
db_configs.each do |db_config|
690+
Base.establish_connection(db_config)
691+
check_pending!
692+
end
693+
ensure
694+
Base.establish_connection(prev_db_config)
695+
end
696+
697+
private
698+
def db_configs_in_current_env
699+
current_environment = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
700+
ActiveRecord::Base.configurations.configs_for(env_name: current_environment)
701+
end
687702
end
688703

689704
def disable_ddl_transaction # :nodoc:

activerecord/test/cases/migration/pending_migrations_test.rb

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ module ActiveRecord
66
class Migration
77
if current_adapter?(:SQLite3Adapter) && !in_memory_db?
88
class PendingMigrationsTest < ActiveRecord::TestCase
9+
self.use_transactional_tests = false
10+
911
setup do
1012
@migration_dir = Dir.mktmpdir("activerecord-migrations-")
13+
@secondary_migration_dir = Dir.mktmpdir("activerecord-migrations-secondary-")
14+
@original_configurations = ActiveRecord::Base.configurations
15+
ActiveRecord::Base.configurations = base_config
16+
ActiveRecord::Base.establish_connection(:primary)
1117

1218
file = ActiveRecord::Base.connection.raw_connection.filename
13-
@conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:", migrations_paths: @migration_dir
1419
source_db = SQLite3::Database.new file
1520
dest_db = ActiveRecord::Base.connection.raw_connection
1621
backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
@@ -23,19 +28,20 @@ class PendingMigrationsTest < ActiveRecord::TestCase
2328
end
2429

2530
teardown do
26-
@conn.release_connection if @conn
27-
ActiveRecord::Base.establish_connection :arunit
31+
ActiveRecord::Base.configurations = @original_configurations
32+
ActiveRecord::Base.establish_connection(:arunit)
2833
FileUtils.rm_rf(@migration_dir)
34+
FileUtils.rm_rf(@secondary_migration_dir)
2935
end
3036

3137
def run_migrations
3238
migrator = Base.connection.migration_context
3339
capture(:stdout) { migrator.migrate }
3440
end
3541

36-
def create_migration(number, name)
42+
def create_migration(number, name, migration_dir: nil)
3743
filename = "#{number}_#{name.underscore}.rb"
38-
File.write(File.join(@migration_dir, filename), <<~RUBY)
44+
File.write(File.join(migration_dir || @migration_dir, filename), <<~RUBY)
3945
class #{name.classify} < ActiveRecord::Migration::Current
4046
end
4147
RUBY
@@ -86,7 +92,7 @@ def test_okay_with_no_migrations
8692
def test_understands_migrations_created_out_of_order
8793
# With a prior file before even initialization
8894
create_migration "05", "create_bar"
89-
run_migrations
95+
quietly { run_migrations }
9096

9197
check_pending = CheckPending.new(@app)
9298

@@ -100,6 +106,65 @@ def test_understands_migrations_created_out_of_order
100106
check_pending.call({})
101107
end
102108
end
109+
110+
def test_with_multiple_database
111+
create_migration "01", "create_bar", migration_dir: @secondary_migration_dir
112+
113+
assert_raises ActiveRecord::PendingMigrationError do
114+
CheckPending.new(@app).call({})
115+
end
116+
117+
begin
118+
ActiveRecord::Base.establish_connection(:secondary)
119+
quietly { run_migrations }
120+
end
121+
ActiveRecord::Base.establish_connection(:primary)
122+
123+
@app.expect :call, nil, [{}]
124+
CheckPending.new(@app).call({})
125+
@app.verify
126+
127+
# Now check exclusion if database_tasks is set to false for the db_config
128+
create_migration "02", "create_foo", migration_dir: @secondary_migration_dir
129+
assert_raises ActiveRecord::PendingMigrationError do
130+
CheckPending.new(@app).call({})
131+
end
132+
133+
new_config = base_config
134+
new_config[ActiveRecord::ConnectionHandling::DEFAULT_ENV.call][:secondary][:database_tasks] = false
135+
ActiveRecord::Base.configurations = new_config
136+
137+
@app.expect :call, nil, [{}]
138+
CheckPending.new(@app).call({})
139+
@app.verify
140+
end
141+
142+
def test_with_stdlib_logger
143+
old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout)
144+
quietly do
145+
assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new { }).call({}) }
146+
end
147+
ensure
148+
ActiveRecord::Base.logger = old
149+
end
150+
151+
private
152+
def base_config
153+
{
154+
ActiveRecord::ConnectionHandling::DEFAULT_ENV.call => {
155+
primary: {
156+
adapter: "sqlite3",
157+
database: "test/fixtures/fixture_database.sqlite3",
158+
migrations_paths: @migration_dir
159+
},
160+
secondary: {
161+
adapter: "sqlite3",
162+
database: "test/fixtures/fixture_database.sqlite3",
163+
migrations_paths: @secondary_migration_dir
164+
}
165+
}
166+
}
167+
end
103168
end
104169
end
105170
end

0 commit comments

Comments
 (0)