Railsのマイグレーションを使わずRidgepoleを導入してみた

みなさんは、どのようにRailsのマイグレーションファイルを運用しているでしょうか。

SQLを直接扱わなくてもデータベースのスキーマの変更をバージョン管理できるなど、マイグレーションはとてもメリットが大きいと思います。

## マイグレーションを使わないという選択肢

非常にメリットの大きいマイグレーションですが、何十名もの人が、1つの巨大なモノリシックアプリケーションを開発する場合、テーブル追加やカラム変更が毎日のように行われ、互いの作業をブロックするようなコンフリクトも度々発生し、運用するのがなかなか難しくなってきます。

また、複数のプロジェクト全てで同じデータベースを参照している場合、各プロジェクトごとのマイグレーションファイルを同じ状態に揃えないとデータベースの完全な整合性が取れないという問題も生じてきます。

そのような問題への解決策として Rails のマイグレーションは使わずに、スキーマ管理用のコマンドラインツールである<a href="https://github.com/winebarrel/ridgepole" target="_blank">Ridgepole</a>を導入してみたいと思います。

## Ridgepoleを使ってみる

### 環境

- Ruby 2.3.5
- Rails 5.1.4
- MySQL 5.7.20

### インストール

`Gemfile`に`ridgepole`を追加し、`bundle install`します。

```ruby:Gemfile
...
gem 'ridgepole'
```

```shell-session
$ bundle install
```

### 既存のデータベースからエクスポート

まずはデータベース接続情報用の設定ファイルを作成します

```yml:config.yml
adapter: mysql2
encoding: utf8mb4
database: app_development
username: root
```

設定ファイルを作成したら、今作成した設定ファイルを指定し、データベースから既存のスキーマ情報をエクスポートします

```shell-session
$ bundle exec ridgepole -c config.yml --export -o Schemafile
```

`Schemafile`が出力されたのが確認できたと思います。

```ruby:Schemafile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# ...略
```

### 新規テーブルを作成する

`Schemafile`に追記し、新規テーブルとして`books`テーブルを作成してみたいと思います。

```ruby:Schemafile
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もあるので、まずは問題なく反映されそうかテストしてみます。

```shell-session
$ 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で問題なかったので、データベースに反映させたいと思います。

```shell-session
$ 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に対応するのも難しくないので、ぜひ導入を検討してみてはいかがでしょうか。