💎

ActiveRecord::MigrationとRidgepoleを比較する

に公開

1. はじめに

こんにちは、ラブグラフでエンジニアインターンをしているうらっしゅです!
Railsアプリケーションのスキーマ管理といえば、ActiveRecord::Migrationが定番です。しかし、チーム開発やCI/CDの整備、複数環境間の差分管理を考えたとき、Migrationの運用に限界を感じたことはないでしょうか?
そこで近年注目を集めているのが、宣言的にスキーマを管理できる Ridgepole です。Schemafile という単一ファイルにスキーマの最終形を定義し、差分を検出・適用するスタイルは、Gitベースでインフラを管理する文化(Infrastructure as Code)にも通じます。
本記事では、ActiveRecord::MigrationRidgepoleを具体的なユースケースで比較しつつ、それぞれのメリット・デメリットを明確にし、どのようなチームや開発体制に適しているかを解説します。Railsエンジニアの技術選定に、ひとつの指針を提供できれば幸いです。

本記事でわかること

  • ridgepoleの導入と使い方
  • 2つのスキーマ管理の技術的視点の違い
  • 2つのスキーマ管理のチーム運用視点の比較

対象読者

  • ridgepole導入を検討しているRailsエンジニア
  • ActiveRecord::Migrationridgepoleで技術的な比較を行いたい方
  • Ridgepole弱点を知らない方

2. スキーマファイルを直接編集して、適用だけで完了Ridgepole

Ridgepole の大きな特徴は、スキーマの最終状態を1ファイル(Schemafile)で宣言的に管理できるという点です。
これにより、「どのマイグレーションを実行したか」ではなく、「今どういう構造であるべきか」を明確に定義できるようになります。

実運用においても、Schemafile を直接編集し、コマンド一発で反映できるのが魅力です。

ridgepoleの導入

  1. ridgepoleというgemをinstallします。
gem `ridgepole`
  1. Schemafileという名前のファイルを作成します。(拡張子はなし)
# プロジェクトルートに配置
db/Schemafile
  1. Schemafile の例
  • Railsの schema.rb に似ていますが、手動で編集する前提のファイルです。
  • 複数のテーブルや index、外部キー制約などもここに記述します。
Schemafile
# ユーザー情報を保持するテーブル
create_table :users, force: :cascade do |t|
  t.string   :name,  null: false
  t.string   :email, null: false
  t.integer  :age,   null: true
  t.timestamps       null: false

  t.index [:email], name: :index_users_on_email, unique: true
end

# タスク情報を保持するテーブル(ユーザーに紐づく)
create_table :tasks, force: :cascade do |t|
  t.string   :title,       null: false
  t.text     :description
  t.boolean  :done,        null: false, default: false
  t.integer  :user_id,     null: false
  t.datetime :due_at
  t.timestamps             null: false

  t.index [:user_id], name: :index_tasks_on_user_id
  t.index [:done],    name: :index_tasks_on_done
end

# 外部キー制約(tasks.user_id → users.id)
add_foreign_key :tasks, :users, on_update: cascade

適用コマンドの例

以下のようなコマンドを実行するとSchemafileの内容をそのままマイグレーションすることができます!
詳しいridgepoleコマンドの使い方も載せてみたので、それぞれの用途に合わせて使ってみてください。

bundle exec ridgepole -c config/database.yml -E development -f db/Schemafile --apply
🛠️ 基本オプションの説明(-c, -E, -f)
オプション 説明
-c データベース設定ファイル(例:config/database.yml)を指定します
-E 使用するRails環境(例:development, production など)を指定します
-f 差分のもととなる Schemafile を指定します(Ridgepole における「理想のスキーマ」)
🚀 主な実行フラグの説明(--apply, --dry-run, --export)
オプション 説明
--apply Schemafile と実際のDBの差分を検出し、必要な変更のみを DBに適用します(差分がなければ何もしません)
--dry-run 実際に適用せずに「どのような変更が実行されるか」を 差分だけ表示します(テーブル変更の事前確認に便利)
--export 現在のDBの構造を Schemafile の形式で 出力します(通常は > db/Schemafile でリダイレクトして保存)
💎 Rake タスクスクリプト例
# lib/tasks/ridgepole.rake
namespace :ridgepole do
  desc "開発環境にSchemafileを適用する"
  task apply: :environment do
    sh "bundle exec ridgepole -c config/database.yml -E development -f db/Schemafile --apply"
  end

  desc "テスト環境にSchemafileを適用する"
  task apply_test: :environment do
    sh "RAILS_ENV=test bundle exec ridgepole -c config/database.yml -E test -f db/Schemafile --apply"
  end

  desc "差分を表示(開発環境)"
  task diff: :environment do
    sh "bundle exec ridgepole -c config/database.yml -E development -f db/Schemafile --dry-run"
  end

  desc "DBの構造をエクスポート(開発環境)"
  task export: :environment do
    sh "bundle exec ridgepole -c config/database.yml -E development --export > db/Schemafile"
  end
end

3. 柔軟な移行処理はActiveRecord Migrationが圧倒的に強い

例えば、旧カラムから新カラムにデータを加工して移動する場合を考えてみましょう。
ActiveRecord Migrationはどう変更していくのか(その履歴)を大事にしているので、こうした移行もしっかり履歴として残すことができます。

ステップ1: 新カラムを追加(旧カラムと共存)
class AddNewStatusToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :new_status, :string
  end
end
ステップ2: データ移行用マイグレーション
class MigrateStatusToNewStatus < ActiveRecord::Migration[7.0]
  def up
    User.find_each do |user|
      user.update!(new_status: transform(user.status))
    end
  end

  def down
    # 巻き戻し用
    User.find_each do |user|
      user.update!(status: reverse_transform(user.new_status))
    end
  end
end
ステップ3: 古いカラムを削除
class RemoveOldStatusFromUsers < ActiveRecord::Migration[7.0]
  def change
    remove_column :users, :status
  end
end

特に注目して欲しいのが、ステップ2: データ移行用マイグレーションです。
これをRidgepoleで行おうとすると、どうなるでしょうか。

Ridgepoleはあくまで「テーブル定義の最終的な状態」を記述するツールであり、データそのものの移行や変換処理(例:旧カラムから新カラムへのコピー処理)を記述する仕組みを持っていません。そのため、ActiveRecordのようにマイグレーションファイルに User.find_each を書いて一括で更新する、といった柔軟な運用ができません。

結果として、データ移行の処理は別途RakeタスクやSQLスクリプトとして手動で書いて・実行して・管理する必要が出てきます。

しかも、データ移行自体はスクリプトで直接行う必要があるため、Gitなどのファイルの変更履歴しか見れないシステムだと履歴に乗りにくい弱点があります。つまり、どのタイミングでどんなデータ移行をしたのかがスキーマ定義と分離され、記録・再現性・レビュー性の観点でも見落とされやすいのです。

このように、Ridgepoleは「スキーマのあるべき姿」を管理するには非常に優秀ですが、「スキーマ変更に伴うデータの移行・補正」には弱く、それを補う仕組みを開発者自身が別で設計・管理する必要があるという点は、導入前に明確に認識しておくべきでしょう。

4. MigrationとRidgepoleの違いを俯瞰する(命令型 vs 宣言型)

以下の表にActiveRecord MigrationRidgepoleの違いをまとめました。
大きく分けるとバージョン管理の思想に明確な違いがあります。

  • ActiveRecord Migrationスキーマ専用のバージョン管理
  • RidgepoleSchemafileが全て。バージョン管理はGitにおまかせ
項目 ActiveRecord Migration(命令型) Ridgepole(宣言型)
基本スタイル 「何をどう変更するか」を時系列で記述する 「こうあるべき」という理想状態を1ファイルで定義する
add_column :users, :age, :integer t.integer :agecreate_table "users" に追記
差分管理 実行した履歴を schema_migrations テーブルで追跡(変更分だけファイル数も増える) Schemafile の差分を比較する(Ridgepole自体は履歴を持たないので Git 等を使う)
マイグレーションログ 時系列でマイグレーションログが見れて、ロールバックも可能 Git で差分履歴を見る
コンフリクト解決難易度 とても難しい(マージ時ではなく、マイグレーション実行時に衝突が発覚、ファイルの再設計が必要になる) いつものマージ時の衝突を解決するだけでOK(差分ベースで管理されるため)
学習コスト チュートリアルでも多く紹介される 各々でドキュメントから使い方を学習する必要がある(とはいえ簡単なので学習コストは低め)
導入コスト デフォルトで搭載(導入コストは小さい) ridgepole という Gem を追加し、Schemafile なども用意する必要がある
チーム開発やCIとの親和性 △ マイグレーションファイルの実行順や衝突管理が必要で、コンフリクト時の復旧が煩雑になりがち ◎ スキーマ状態を Git 管理できるためレビューや CI に組み込みやすく、差分ベースでの自動チェックも可能

まとめ:MigrationRidgepole、どう使い分ける?

◾ それぞれのメリット・デメリットまとめ

観点 ActiveRecord::Migrationのみ Ridgepoleのみ
💡 メリット - 柔軟なデータ移行(カラム設計を間違えることができる。)
- ロールバックや履歴管理が容易
- Schemafile で「理想状態」を一元管理(が、カラムデータ間違えられない。)
- Git と親和性が高い
- 差分確認やCI導入が容易
⚠ デメリット - マイグレーションの衝突や順序ミスが起きやすい
- 差分ベースの比較やレビューに向かない
- データの移行処理を内包できない(Rakeタスクなど別途管理)
- 宣言的であるがゆえの制約がある

Migrationが有利に働くケース

  • データの加工・移行が必要な場合(例:旧カラムから新カラムへの変換)
  • スキーマ変更に伴う複雑なロジックをマイグレーションファイルとして記録したいとき
  • 学習コストが低く、Rails単体で完結する手段が望ましいチーム

Ridgepoleが有利に働くケース

  • 複数環境(dev/test/prod)間での差分管理が求められる場合
  • CI/CDとの連携や「変更差分の明示化」「コードレビューの簡素化」を重視する開発体制
  • マイグレーションの順序ミスやコンフリクトに悩まされた経験があるチーム

◾ Rails以外との連携を見据えるなら?

  • Migration:Railsアプリ単体で閉じており、他システムとの連携がない場合に向いている。
  • RidgepoleSchemafile はRubyベースのDSLとはいえ、DBスキーマの最終状態をコードで表現するため、Railsに依存しすぎず、他システムとスキーマ仕様を共有しやすくなります。

◾ チームでの導入ステップ(Ridgepoleを想定)

  1. 小規模プロジェクトや非本番環境から試験導入

  2. Schemafileridgepole --export で作成し、既存スキーマを取り込む

  3. GitHub Actions などに ridgepole --dry-run を組み込み、自動差分チェック

  4. 本番導入時は マイグレーションとの併用ポリシーを明確化

    • データ移行 → Migration / Rake タスクで実施
    • スキーマ構造の変更 → Ridgepole で管理

このように、MigrationとRidgepoleは排他ではなく補完的に使える関係です。
プロジェクトの性質やチーム構成に応じて「いつ何を使うか」を合意形成することが、スキーマ管理の成熟度を高める第一歩になります。

ラブグラフのエンジニアブログ

Discussion