[Rails] change_table bulk: true のリバート時にテーブル名のプレフィックス/サフィックスが正しく適用されない問題を修正
修正の背景
Railsのマイグレーションで change_table に bulk: true オプションを指定すると、複数のカラム操作を1つのALTER TABLE文にまとめて実行できます。しかし、このバルク操作をリバート(ロールバック)する際に、テーブル名のプレフィックスやサフィックスが正しく適用されないバグが存在していました。
この問題は #56573 で報告され、ActiveRecord::Base.table_name_prefix または table_name_suffix を設定している環境でマイグレーションをロールバックすると、テーブル名が正しく解決されず、操作が失敗する原因となっていました。
技術的な詳細
問題の原因
CommandRecorder クラスの change_table メソッドでは、バルク操作をリバートする際に以下のようなコードが使用されていました。
変更前:
@commands << [:change_table, [table_name], -> t { bulk_change_table(table_name, commands) }]
このコードでは、リバート時に元の table_name 引数をそのまま bulk_change_table に渡していました。しかし、この table_name はプレフィックス/サフィックスが適用される前の論理的なテーブル名であり、実際のデータベース上のテーブル名とは異なる可能性がありました。
修正内容
修正後は、テーブル定義オブジェクト t の name プロパティを参照するように変更されました。
変更後:
@commands << [:change_table, [table_name], -> t { bulk_change_table(t.name, commands) }]
t.name は、プレフィックスやサフィックスが既に適用された実際のテーブル名を返します。これにより、リバート時にも正しいテーブル名でSQL文が生成されるようになりました。
動作確認
修正の妥当性を確認するため、テーブル名プレフィックスを使用したテストケースが追加されました。
def test_bulk_revert_with_table_name_prefix
ActiveRecord::Base.table_name_prefix = "prefix_"
@connection.create_table(:prefix_testings, force: true)
migration = Class.new(ActiveRecord::Migration::Current) {
def write(text = ""); end
def change
change_table :testings, bulk: true do |t|
t.column :foo, :string
end
end
}.new
migration.migrate(:up)
assert @connection.column_exists?(:prefix_testings, :foo)
assert_queries_count(1) do
migration.migrate(:down)
end
assert_not @connection.column_exists?(:prefix_testings, :foo)
ensure
@connection.drop_table :prefix_testings, if_exists: true
ActiveRecord::Base.table_name_prefix = ""
end
このテストでは以下を検証しています:
-
table_name_prefix = "prefix_"を設定 - 論理名
:testingsでマイグレーションを定義(実際のテーブル名はprefix_testings) - マイグレーションを実行してカラム追加を確認
- ロールバック時に1つのクエリで処理されることを確認(バルク操作)
- カラムが正しく削除されることを確認
影響範囲
この修正は、以下の条件をすべて満たす場合に影響します:
-
ActiveRecord::Base.table_name_prefixまたはtable_name_suffixを使用している - マイグレーションで
change_table bulk: trueを使用している - マイグレーションをロールバック(
rake db:rollback)する
該当する環境では、この修正によりロールバックが正しく動作するようになります。