みなさんは、どのようにRailsのマイグレーションファイルを運用しているでしょうか。
SQLを直接扱わなくてもデータベースのスキーマの変更をバージョン管理できるなど、マイグレーションはとてもメリットが大きいと思います。
マイグレーションを使わないという選択肢
非常にメリットの大きいマイグレーションですが、何十名もの人が、1つの巨大なモノリシックアプリケーションを開発する場合、テーブル追加やカラム変更が毎日のように行われ、互いの作業をブロックするようなコンフリクトも度々発生し、運用するのがなかなか難しくなってきます。
また、複数のプロジェクト全てで同じデータベースを参照している場合、各プロジェクトごとのマイグレーションファイルを同じ状態に揃えないとデータベースの完全な整合性が取れないという問題も生じてきます。
そのような問題への解決策として Rails のマイグレーションは使わずに、スキーマ管理用のコマンドラインツールであるRidgepoleを導入してみたいと思います。
Ridgepoleを使ってみる
環境
- Ruby 2.3.5
- Rails 5.1.4
- MySQL 5.7.20
インストール
Gemfile
にridgepole
を追加し、bundle install
します。
...
gem 'ridgepole'
$ bundle install
既存のデータベースからエクスポート
まずはデータベース接続情報用の設定ファイルを作成します
adapter: mysql2
encoding: utf8mb4
database: app_development
username: root
設定ファイルを作成したら、今作成した設定ファイルを指定し、データベースから既存のスキーマ情報をエクスポートします
$ bundle exec ridgepole -c config.yml --export -o Schemafile
Schemafile
が出力されたのが確認できたと思います。
# -*- mode: ruby -*-
# vi: set ft=ruby :
# ...略
新規テーブルを作成する
Schemafile
に追記し、新規テーブルとしてbooks
テーブルを作成してみたいと思います。
create_table "books", force: :cascade, options: "ENGINE=InnoDB" do |t|
t.string "title", null: false
t.string "isbn", null: false
t.datetime "published_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["isbn"], name: "isbn", unique: true, using: :btree
t.index ["published_at"], name: "published_at", using: :btree
end
title
, isbn
, published_at
カラムを持ち、isbn
はユニークインデックス、published_at
にはインデックスが貼られているテーブルです。
これを反映しますが、RidgepoleにはオプションでDryRunもあるので、まずは問題なく反映されそうかテストしてみます。
$ bundle exec ridgepole -c config.yml --apply --dry-run
Apply `Schemafile` (dry-run)
create_table("books", {:options=>"ENGINE=InnoDB"}) do |t|
t.column("title", :"string", {:null=>false, :limit=>255})
t.column("isbn", :"string", {:null=>false, :limit=>255})
t.column("published_at", :"datetime", {:null=>false})
t.column("created_at", :"datetime", {:null=>false})
t.column("updated_at", :"datetime", {:null=>false})
end
add_index("books", ["isbn"], {:name=>"isbn", :unique=>true, :using=>:btree})
add_index("books", ["published_at"], {:name=>"published_at", :using=>:btree})
# CREATE TABLE `books` (
# `id` int AUTO_INCREMENT PRIMARY KEY,
# `title` varchar(255) NOT NULL,
# `isbn` varchar(255) NOT NULL,
# `published_at` datetime NOT NULL,
# `created_at` datetime NOT NULL,
# `updated_at` datetime NOT NULL)
# ENGINE=InnoDB
# CREATE UNIQUE INDEX `isbn` USING btree ON `books` (
# `isbn`)
# CREATE INDEX `published_at` USING btree ON `books` (
# `published_at`)
DryRunすると実際に発行されるクエリも参照できるようです。
DryRunで問題なかったので、データベースに反映させたいと思います。
$ bundle exec ridgepole -c config.yml --apply
Apply `Schemafile`
-- create_table("books", {:options=>"ENGINE=InnoDB"})
-> 0.0263s
-- add_index("books", ["isbn"], {:name=>"isbn", :unique=>true, :using=>:btree})
-> 0.0179s
-- add_index("books", ["published_at"], {:name=>"published_at", :using=>:btree})
-> 0.0153s
これでRidgepoleを使ってテーブルを追加することができました!
まとめ
通常のマイグレーションと変わらずRidgepoleでテーブルの追加など、スキーマの変更を行うことができました。
Railsのマイグレーションではなかなかツラくなってきたというプロジェクトは、Ridgepoleに対応するのも難しくないので、ぜひ導入を検討してみてはいかがでしょうか。