すでに動いているシステムでridgepoleを使うためのメモ


## はじめに

[ridgepole](https://github.com/winebarrel/ridgepole) はCookpad社が使用しているスキーマ管理ツールです。

スキーマ情報は通常のRailsの `schema.rb` で使用されているDSLで書かれた `Schemafile` ファイルで管理され、ここで書かれた定義とDBのスキーマの定義と差分があった場合、DBに差分が適用されるという感じです。

これを使用することによりDBのスキーマを直接変えてからスキーマファイルを生成したり、 `rails g migration` を実行してできたマイグレーションファイルの増加をなくすことができたり、DBのスキーマ変更をgitのログなどで見ることができたりします。

今回はこの `ridgepole` を本番に適用するときに役立ちそうなメモを残したいと思います。

## `Schemafile` の作成

まず既存のDBから `Schemafile` をファイルを生成します。 `Schemafile` を作成するために以下のコマンドを叩きます。

```zsh
$  bundle exec ridgepole -c config/database.yml --export --split --output db/schemas/Schemafile
```

`-c` でデータベースのコンフィグファイルを指定し、 `--export` で `Schemafile` ファイルを出力するようにします。また、 `--split` を使用することで各テーブルごとに `*.schemas` ファイルを作成し、 `Schemafile` で `require` することもできます。

ただ `--split` をすると、外部キーが一番最後に出力されるファイルにまとまってしまうのが謎です。Railsでmigrationファイルを作ってmigrationする時出力される `schema.rb` も同じなのでそのあたりの仕様に従っているのかもしれません。

このコマンドを叩くときは何も本番環境で実行必要はなく、開発環境で実行すればよいです（開発環境と本番環境でスキーマに差分はないはず・・・）。

## 外部キーの問題

実施に `Schemafile` を作成したらまずはデータの入っているステージング環境でテストします（まぁ当たり前の話です）。

ここで多くの人が躓くのは外部キーに名前をつけていない問題です。railsでマイグレーションファイルを作るとき、外部キー制約を記述するのに `references` を使うと思うのですが、これだと明示的にキーの名前をつけることができず、 出力した `Schemafile` を使って `ridgepole` を実行するときエラーになってしまいます。

```zsh
[ERROR] Foreign key name in `テーブル名` is undefined
```

Railsでは `references` で自動的につけた外部キーの名前は `fk_rails_ランダムな文字列` になっています。普通に `ridgepole` を用いて `Schemafile` を出力すると外部キーの名前は **出力されません** 。出力させるためには `--dump-with-default-fk-name` オプションを付ける必要があります。このオプションを付けると `fk_rails_ランダムな文字列` が付与されて外部キーの定義が出力されます。

ところがここで出力した `Schemafile` を使っていざ適用すると、

```zsh
[ERROR] Foreign key name in `テーブル名` is undefined
```

がまた出てしまいます。

ソースを見てみた所、 `Schemafile` と実際のDBのスキーマの差分を取るところでエラーになっているらしいです。 

実際に差分を取っているところは以下の場所です。

https://github.com/winebarrel/ridgepole/blob/d4a1f12d86dda96efedfdf61ee85faff74cdd6ba/lib/ridgepole/client.rb#L48

ここで `expected_definition` は読み込んだ `Schemafile` なので外部キーの定義は書いてあります。問題は `current_definition` で、これが今のDBに定義されているスキーマを `ActiveRecord::SchemaDumper.dump` で取得して比較対象を作っています。ここでダンプしてくるときにどうやら外部キーの定義が抜けてしまうようです。

これをなんとかするためにはいったん外部キーを張り直すことで対処しました。

```rb

remove_foreign_key :from, :to
add_foreign_key :from, :to, name: 'fk_rails_from_to'
```

こういうファイルを愚直に作り、外部キーに明示的に名前をつけていきます。 

`schema.rb` に、

```rb
add_foreign_key "from", "to", name: "fk_rails_from_to"
```

のように `name` が追加されれば大丈夫です。

愚直につけていくしか方法がないように自分は思ったのですが、もし他になにかいいやり方がアレばそちらを採用したいぐらいだるい作業です。

外部キーを張り替えてから再度 `ridgepole` を実行し、問題なく完了すればOKです。

## デプロイ

実際にコマンドで適用できることを確認したら実際のデプロイタスクに組み込みます。 

`capistrano` を使用していたらpluginでよしなにやってくれることを期待してしまいがちですが、あまり良いのがないのでrakeタスクを作ってそれを実行するようにデプロイスクリプトに組み込むのが良いと思います。

rakeタスクを作ってしまえば `heroku` でデプロイする時に [buildpack-ruby-rake-deploy-tasks](https://github.com/gunpowderlabs/buildpack-ruby-rake-deploy-tasks) で実行すれば自動化できて大変良いです。

[herokuにDeployしたタイミングでdb:migrateを実行したい](http://qiita.com/mapyo/items/42c9a70e8941ae19cd2b)

ステージングで確認しているとは言え `capistrano` を実行して適用するのは緊張感のある作業です。

## やってみてどうだったか

増殖するマイグレーションファイルは一掃できましたし、スキーマの変更も楽にできるのが非常に良い感じです。 `raild g migration` のような便利タスクはないので自分でファイルを作る必要はありますが、それもgeneratorを作ってしまえば解決できる問題だと思います。

ちょっと気になるところとしては、Cookpad社ではDBで直接 `ALTER TABLE` してから `ridgepole` でスキーマを出力し、プルリクするというフローでやっているのに対し、自分たちは `*.schemas` ファイルを追加したり編集したりするフローにしているところで、あまり意図通りの使い方をしていないんじゃないかというところです。

今のところ特に問題は起きていないので大丈夫そうですが、少し気をつけたほうがいいかもしれません。

## 参考にしたURL

[クックパッドにおける最近のActiveRecord運用事情](http://techlife.cookpad.com/entry/2014/08/28/194147)

[Ridgepole本番運用における懸念点と解決方法の検討](http://qiita.com/EastResident/items/c53c1e69d677f85a4d72)

[さらば「rails migrate」、よろしく「ridgepole」](https://nowdeveloping.blogspot.jp/2016/10/rails-migrateridgepole.html)