Open3

strong_migrations on ridgepole

pockepocke

StrongMigrations を Ridgepole でも使えないかと思って試してみた。

前提

  • StrongMigrations はMigration時のadd_columnなどのメソッド呼び出しにフックして、そこでチェックを行っている
  • Ridgepole は db/schema.rb と同様のメソッドを使って書いたDB定義から現在のDB定義との差分を抽出して、それをActive Record の API を使ってSQLを実行している
  • 両者ともにActive Record の API を使っているので、うまくそこが噛み合うような仕組みを入れれば 動くのでは。
pockepocke

対応方法

次のようなパッチを strong_migrations に当てれば動かすことができた。

diff --git a/lib/strong_migrations/migration.rb b/lib/strong_migrations/migration.rb
index 5ea67eb..6cff0d7 100644
--- a/lib/strong_migrations/migration.rb
+++ b/lib/strong_migrations/migration.rb
@@ -7,11 +7,6 @@ module StrongMigrations
     end
 
     def method_missing(method, *args)
-      return super if is_a?(ActiveRecord::Schema)
-
-      # Active Record 7.0.2+ versioned schema
-      return super if defined?(ActiveRecord::Schema::Definition) && is_a?(ActiveRecord::Schema::Definition)
-
       catch(:safe) do
         strong_migrations_checker.perform(method, *args) do
           super
$ env RUBYOPT=-rstrong_migrations bundle exec ridgepole -c ./database.yml --apply
[strong_migrations] DANGER: Lock timeout is longer than 10 seconds: 31536000
-- change_column("articles", "title", :string, {null: true, default: nil, unsigned: false, comment: nil})
   -> 0.0471s
[ERROR] 
=== Dangerous operation detected #strong_migrations ===

Changing the type of an existing column blocks writes
while the entire table is rewritten. A safer approach is to:

1. Create a new column
2. Write to both columns
3. Backfill data from the old column to the new column
4. Move reads from the old column to the new column
5. Stop writing to the old column
6. Drop the old column

  1: change_column("articles", "title", :string, **{null: true, default: nil, unsigned: false, comment: nil})
* 2: change_column("articles", "text", :text, **{null: true, default: nil, unsigned: false, comment: nil})
  3: change_column("articles", "created_at", :datetime, **{null: true, default: nil, unsigned: false, comment: nil})

真面目に使うことを考える場合

このパッチはとりあえず動くことを検証してみただけなので、真面目に使う場合はもうちょっと考えなければいけないと思う。

  • 単にis_a?(ActiveRecord::Schema)のチェックを消すのではなく、defined?(Ridgepole)であればこのチェックをスキップする、としたほうが良さそう。
    • defined?(Ridgepole)を見るだけで良いのかはちょっとわからない。
  • この変更を真面目にstrong_migrations 側に入れる場合、strong_migrations の機能全般がちゃんと動くことを確認したほうが良さそう
    • とりあえず最低限チェックが動くことしか見ていない。
pockepocke

(これ以上のアクションを取ることはひとまず考えていないので、やる気がある人がいたらチャレンジしてみてほしい)