strong_migrations gemのススメ

こんにちは。会計Plusでエンジニアをしているぽっけです。最近はシャケをしばくバイトで、やっとでんせつに上がりました。

今日はstrong_migrationsというRails向けのgemを紹介します。

strong_migrationsとは

https://github.com/ankane/strong_migrations

strong_migrationsは、危険なmigrationを検出するgemです。

データベースのmigrationは、ときに危険になります。たとえば実行するDDLによってはデータベースへの書き込みをブロックしてしまうことがあります。またテーブル定義の変更は、うまくやらないとアプリケーションが意図せぬ動作をするかも知れません。

strong_migrationsはそのような危険なmigrationを検出します。

⁠使い方

使い方はかんたんです。strong_migrations gemをGemfileに追加し、bin/rails generate strong_migrations:install コマンドを実行するだけです。

strong_migrationsをインストールしておくと、危険なmigrationは自動的にエラーになって失敗するようになります。

また危険とみなされたものも、safety_assured { add_column ... }のように、safety_assuredメソッドのブロックの中に書くことでエラーを抑制できます。

検出される問題

検出されるすべての例は、READMEに列挙されています。 https://github.com/ankane/strong_migrations#checks

わかりやすいところだと、カラムの削除デフォルト値付きのカラムの追加などがあります。

strong_migrationsはRails向けのgemですが、チェックされる項目の多くはRDBMSに起因する問題であるため、Railsを使っていない方も目を通してみると発見があるかも知れません。

strong_migrationsの素晴らしい点

危険なmigrationを検出できるという点でかなり価値のあるgemだと思いますが、個人的にstrong_migrationsの真の素晴らしさは別の所にあると思います。それは検出結果の手厚さです。

たとえばデフォルト値付きのカラムの追加が行われた場合のエラーを見てみましょう。strong_migrationsを有効にしていると、次のmigrationはエラーが発生します。

class AddTitleToArticle < ActiveRecord::Migration[7.0]
  def change
    add_column :articles, :title, :string, default: ''
  end
end
検証用コードのフルバージョンはこちら(長いので折りたたんであります)。

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem "activerecord", "~> 7.0.0"
  gem 'strong_migrations'
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")

ActiveRecord::Schema.define do
  create_table :articles, force: true do |t|
    t.string :content, null: false
  end
end

class Article < ActiveRecord::Base
end

class AddTitleToArticle < ActiveRecord::Migration[7.0]
  def change
    add_column :articles, :title, :string, default: ''
  end
end

class BugTest < Minitest::Test
  def test_migration
    AddTitleToArticle.migrate(:up)
  end
end

この検証用コードはRailsのバグ報告テンプレートをベースにしています。こういうときにめっちゃ便利です。

発生するエラーは以下のとおりです。

=== Dangerous operation detected #strong_migrations ===

Adding a column with a non-null default blocks reads and writes while the entire table is rewritten.
Instead, add the column without a default value, then change the default.

class AddTitleToArticle < ActiveRecord::Migration[7.0]
  def up
    add_column :articles, :title, :string
    change_column_default :articles, :title, ""
  end

  def down
    remove_column :articles, :title
  end
end

Then backfill the existing rows in the Rails console or a separate migration with disable_ddl_transaction!.

class BackfillAddTitleToArticle < ActiveRecord::Migration[7.0]
  disable_ddl_transaction!

  def up
    Article.unscoped.in_batches do |relation| 
      relation.update_all title: ""
      sleep(0.01)
    end
  end
end

このように、かなり手厚い出力がなされます。strong_migrationsの特に素晴らしい点は、エラーメッセージに以下の項目が網羅されていることです。

  • 何が問題なのか
  • ⁠なぜ問題になるのか
  • どのようにして対応をしたらいいのか

たとえばこのエラーメッセージでは、「デフォルト値付きのカラムの追加」が問題であり、それが「テーブル全体を再書き込みするためにread/writeをブロックする」のが問題になる理由であり、「提案されているコードを参考に書き換える」必要があることが説明されています。

この3点が説明されていることで、このエラーメッセージを見たユーザーは次に何をしたらいいのかが理解できます。つまり、strong_migrationsの警告はとても対応しやすくなっています。このようなツールはエラーへの対応方法が分かりづらく無駄に時間を吸われてしまうものも多い中、このように充実した説明はツールを使う人にとってとても優しいです。

この丁寧さは、自分がなにかツールを作る際にも意識したいものです。

(欲を言えば「このエラーをsafety_assuredで握りつぶして良い場合」も説明されていると、より嬉しいかも知れないですね。)

まとめ

strong_migrations gemの紹介でした。このgemを使うとかんたんに危険なmigrationを検出できます。またこのgemの出力の手厚さについても紹介しました。

少し前にstrong_migrationsにpull requestを送ってマージされたのが嬉しかったので、今回は紹介をしてみました。

ぜひあなたのRails appにも導入してみてはいかがでしょうか?


マネーフォワードでは、エンジニアを募集しています。
ご応募お待ちしています。

【会社情報】
Wantedly
株式会社マネーフォワード
福岡開発拠点
関西開発拠点(大阪/京都)

【SNS】
マネーフォワード公式note
Twitter – 【公式】マネーフォワード
Twitter – Money Forward Developers
connpass – マネーフォワード
YouTube – Money Forward Developers

Pocket