Skip to content

Commit 6793482

Browse files
committed
Fix pending migration checks for multi-db
Co-authored-by: Guo Xiang Tan <[email protected]> Fixes rails#37524
1 parent f2cafdd commit 6793482

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
@@ -598,7 +598,7 @@ def call(env)
598598
@mutex.synchronize do
599599
@watcher ||= build_watcher do
600600
@needs_check = true
601-
ActiveRecord::Migration.check_pending!(connection)
601+
ActiveRecord::Migration.check_pending_migrations
602602
@needs_check = false
603603
end
604604

@@ -614,13 +614,11 @@ def call(env)
614614

615615
private
616616
def build_watcher(&block)
617-
paths = Array(connection.migration_context.migrations_paths)
617+
current_environment = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
618+
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: current_environment)
619+
paths = all_configs.flat_map { |config| config.migrations_paths || Migrator.migrations_paths }.uniq
618620
@file_watcher.new([], paths.index_with(["rb"]), &block)
619621
end
620-
621-
def connection
622-
ActiveRecord::Base.connection
623-
end
624622
end
625623

626624
class << self
@@ -638,7 +636,7 @@ def check_pending!(connection = Base.connection)
638636

639637
def load_schema_if_pending!
640638
current_db_config = Base.connection_db_config
641-
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
639+
all_configs = db_configs_in_current_env
642640

643641
needs_update = !all_configs.all? do |db_config|
644642
Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord.schema_format)
@@ -656,7 +654,8 @@ def load_schema_if_pending!
656654
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
657655
Base.establish_connection(current_db_config)
658656

659-
check_pending!
657+
# Ensure all migrations have succeeded
658+
check_pending_migrations(db_configs: all_configs)
660659
end
661660

662661
def maintain_test_schema! # :nodoc:
@@ -681,6 +680,22 @@ def migrate(direction)
681680
def disable_ddl_transaction!
682681
@disable_ddl_transaction = true
683682
end
683+
684+
def check_pending_migrations(db_configs: db_configs_in_current_env) # :nodoc:
685+
prev_db_config = Base.connection_db_config
686+
db_configs.each do |db_config|
687+
Base.establish_connection(db_config)
688+
check_pending!
689+
end
690+
ensure
691+
Base.establish_connection(prev_db_config)
692+
end
693+
694+
private
695+
def db_configs_in_current_env
696+
current_environment = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
697+
ActiveRecord::Base.configurations.configs_for(env_name: current_environment)
698+
end
684699
end
685700

686701
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)