ActiveRecord::MigrationとRidgepoleを比較する
1. はじめに
こんにちは、ラブグラフでエンジニアインターンをしているうらっしゅです!
Railsアプリケーションのスキーマ管理といえば、ActiveRecord::Migrationが定番です。しかし、チーム開発やCI/CDの整備、複数環境間の差分管理を考えたとき、Migrationの運用に限界を感じたことはないでしょうか?
そこで近年注目を集めているのが、宣言的にスキーマを管理できる Ridgepole です。Schemafile という単一ファイルにスキーマの最終形を定義し、差分を検出・適用するスタイルは、Gitベースでインフラを管理する文化(Infrastructure as Code)にも通じます。
本記事では、ActiveRecord::MigrationとRidgepoleを具体的なユースケースで比較しつつ、それぞれのメリット・デメリットを明確にし、どのようなチームや開発体制に適しているかを解説します。Railsエンジニアの技術選定に、ひとつの指針を提供できれば幸いです。
本記事でわかること
-
ridgepoleの導入と使い方 - 2つのスキーマ管理の技術的視点の違い
- 2つのスキーマ管理のチーム運用視点の比較
対象読者
-
ridgepole導入を検討しているRailsエンジニア -
ActiveRecord::Migrationとridgepoleで技術的な比較を行いたい方 -
Ridgepoleの弱点を知らない方
2. スキーマファイルを直接編集して、適用だけで完了Ridgepole
Ridgepole の大きな特徴は、スキーマの最終状態を1ファイル(Schemafile)で宣言的に管理できるという点です。
これにより、「どのマイグレーションを実行したか」ではなく、「今どういう構造であるべきか」を明確に定義できるようになります。
実運用においても、Schemafile を直接編集し、コマンド一発で反映できるのが魅力です。
ridgepoleの導入
-
ridgepoleというgemをinstallします。
gem `ridgepole`
-
Schemafileという名前のファイルを作成します。(拡張子はなし)
# プロジェクトルートに配置
db/Schemafile
-
Schemafileの例
- Railsの
schema.rbに似ていますが、手動で編集する前提のファイルです。 - 複数のテーブルや index、外部キー制約などもここに記述します。
# ユーザー情報を保持するテーブル
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 MigrationとRidgepoleの違いをまとめました。
大きく分けるとバージョン管理の思想に明確な違いがあります。
-
ActiveRecord Migrationはスキーマ専用のバージョン管理 -
RidgepoleはSchemafileが全て。バージョン管理はGitにおまかせ
| 項目 | ActiveRecord Migration(命令型) | Ridgepole(宣言型) |
|---|---|---|
| 基本スタイル | 「何をどう変更するか」を時系列で記述する | 「こうあるべき」という理想状態を1ファイルで定義する |
| 例 | add_column :users, :age, :integer |
t.integer :age を create_table "users" に追記 |
| 差分管理 | 実行した履歴を schema_migrations テーブルで追跡(変更分だけファイル数も増える) |
Schemafile の差分を比較する(Ridgepole自体は履歴を持たないので Git 等を使う) |
| マイグレーションログ | 時系列でマイグレーションログが見れて、ロールバックも可能 | Git で差分履歴を見る |
| コンフリクト解決難易度 | とても難しい(マージ時ではなく、マイグレーション実行時に衝突が発覚、ファイルの再設計が必要になる) | いつものマージ時の衝突を解決するだけでOK(差分ベースで管理されるため) |
| 学習コスト | チュートリアルでも多く紹介される | 各々でドキュメントから使い方を学習する必要がある(とはいえ簡単なので学習コストは低め) |
| 導入コスト | デフォルトで搭載(導入コストは小さい) |
ridgepole という Gem を追加し、Schemafile なども用意する必要がある |
| チーム開発やCIとの親和性 | △ マイグレーションファイルの実行順や衝突管理が必要で、コンフリクト時の復旧が煩雑になりがち | ◎ スキーマ状態を Git 管理できるためレビューや CI に組み込みやすく、差分ベースでの自動チェックも可能 |
まとめ:Migration と Ridgepole、どう使い分ける?
◾ それぞれのメリット・デメリットまとめ
| 観点 | ActiveRecord::Migrationのみ | Ridgepoleのみ |
|---|---|---|
| 💡 メリット | - 柔軟なデータ移行(カラム設計を間違えることができる。) - ロールバックや履歴管理が容易 |
- Schemafile で「理想状態」を一元管理(が、カラムデータ間違えられない。) - Git と親和性が高い - 差分確認やCI導入が容易 |
| ⚠ デメリット | - マイグレーションの衝突や順序ミスが起きやすい - 差分ベースの比較やレビューに向かない |
- データの移行処理を内包できない(Rakeタスクなど別途管理) - 宣言的であるがゆえの制約がある |
◾ Migrationが有利に働くケース
- データの加工・移行が必要な場合(例:旧カラムから新カラムへの変換)
- スキーマ変更に伴う複雑なロジックをマイグレーションファイルとして記録したいとき
- 学習コストが低く、Rails単体で完結する手段が望ましいチーム
◾ Ridgepoleが有利に働くケース
- 複数環境(dev/test/prod)間での差分管理が求められる場合
- CI/CDとの連携や「変更差分の明示化」「コードレビューの簡素化」を重視する開発体制
- マイグレーションの順序ミスやコンフリクトに悩まされた経験があるチーム
◾ Rails以外との連携を見据えるなら?
- Migration:Railsアプリ単体で閉じており、他システムとの連携がない場合に向いている。
-
Ridgepole:
SchemafileはRubyベースのDSLとはいえ、DBスキーマの最終状態をコードで表現するため、Railsに依存しすぎず、他システムとスキーマ仕様を共有しやすくなります。
◾ チームでの導入ステップ(Ridgepoleを想定)
-
小規模プロジェクトや非本番環境から試験導入
-
Schemafileをridgepole --exportで作成し、既存スキーマを取り込む -
GitHub Actions などに
ridgepole --dry-runを組み込み、自動差分チェック -
本番導入時は マイグレーションとの併用ポリシーを明確化
- データ移行 → Migration / Rake タスクで実施
- スキーマ構造の変更 → Ridgepole で管理
このように、MigrationとRidgepoleは排他ではなく補完的に使える関係です。
プロジェクトの性質やチーム構成に応じて「いつ何を使うか」を合意形成することが、スキーマ管理の成熟度を高める第一歩になります。
Discussion