Rails migration から ridgepole に移行した
Tags: rails, ridgepole
Published: 2020-03-26 00:04:56

rails migration から ridgepole に移行したので移行時のメモ

# Ridgepole 導入の理由

rails migration では migration の度に migration ファイルの作成して、実行後は db/schema.rb が自動更新される。

システムが未成熟なこともあり、ガンガン migration を行うので、下記のような問題が出てきた。

* migration ファイルが大量に出来てしまって管理が大変
* db/schema.rb を github に上げ忘れること多発
* db/schema.rb でコンフリクト多発
* db:rollback 使うことがほとんどなかった

無駄な作業の煩わしさから解放されるために、ridgepole という gem が色々な記事でオススメされていたので、導入してみようと思います。

* <a ref="https://qiita.com/akishin/items/4612ae2a6f7a5173ff7f">Ridgepole を使ってみる
</a>
* <a ref="https://qiita.com/ayies128/items/c84db2ea41210eae0577">Rails マイグレーションの廃止とRidgepoleの導入・利用方法</a>
* <a href="https://techlife.cookpad.com/entry/2014/08/28/194147">クックパッドにおける最近のActiveRecord運用事情</a>

# Ridgepole インストール

Gemfile に以下を追加する

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

bunlder でインストール

```
$ bundle install --path vendor/bundle
```

# 導入手順

https://github.com/winebarrel/ridgepole#usage

ここに書いてある通りなので簡単に移行できるが、今後を考えてテーブル毎に Schema ファイルを分割して運用することを念頭に移行を進めてみる。

# Schemafile の作成

まず、現状のデータベースから ridgepole 用の Schemafile を作るのだが、今後の管理を考えてテーブル毎にファイルを分割して作成する。
試験用に既存データベースとして、users と posts というテーブルを用意した。

```
$ bundle exec ridgepole -c config/database.yml --export --split -o db/Schemafile
Export Schema
  write `db/posts.schema`
  write `db/users.schema`
  write `db/Schemafile`
```

中身を見てみるとこんな感じ。

```ruby:db/Schemafile
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'posts.schema'
require 'users.schema'
```

```ruby:db/posts.schema
create_table "posts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["name"], name: "index_posts_on_name"
end
```

```ruby:db/users.schema
# -*- mode: ruby -*-
# vi: set ft=ruby :
create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED", force: :cascade do |t|
  t.string "name"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["name"], name: "index_users_on_name"
end

add_foreign_key "posts", "users", column: "user_id"
```

注意したいのは、外部制約キー (add_foreign_key) が users にあること。
どうも、<a href="https://qiita.com/ayies128/items/dc1b6f4b18b95f5b425b">こちらの記事</a>によると、アルファベット順で一番最後のファイルに集約される模様。

ここは好みによると思いますが、add_foreign_key は db/posts.schema に移しました。

* schema ファイルを git で管理するので、export することは稀
* posts のスキーマを確認する際に、posts.schema に書かれていないと存在を忘れる & 再定義してしまい、ミスに繋がる

# 適用 & 確認

Schemafile を適用してみて変更がないことを確認する。
ローカルの開発環境でテストをするので、-E development を付ける。

```
$ bundle exec ridgepole -c config/database.yml -E development -f db/Schemafile --apply
Apply `db/Schemafile` (dry-run)
No change
```

# rake task 作成

毎回、上記のようなコマンドを実行するのは大変なので rake task で簡単にします。
<a href="https://qiita.com/lhside/items/0dcad79d9b801e34bc7c">こちらの記事</a>を参考に作成しました。

```ruby:lib/tasks/ridgepole.rake
namespace :ridgepole do
  desc "Apply ridgepole schemafile"
  task apply: :environment do
    ridgepole('--apply')
  end

  desc "Export ridgepole schemafile"
  task export: :environment do
    ridgepole('--export')
  end

  private
  def config_file
    if Rails.env.development?
      'config/database.yml'
    elsif Rails.env.staging?
      'config/database.staging.yml'
    elsif Rails.env.production?
      'config/database.production.yml'
    else
      raise 'no configuration specified'
    end
  end

  def ridgepole(*options)
    command = ['bundle exec ridgepole --file db/schemas/Schemafile', "-c #{config_file}", "-E #{Rails.env}"]
    system (command + options).join(' ')
  end
end
```

これで環境に関わらず、下記のコマンドで Schemafile を適用できます。

```
$ bundle exec rails ridgepole:apply
```

# db:seed の代用 rake task

ridgepole でスキーマを更新した場合、db:seed による rake task を実行しても、下記のように pending migration による警告が発生して実行できません。

```
$ bundle exec rails db:seed
You have 2 pending migrations:
  20190703015353 CreateUsers
  20190704105400 CreatePosts
Run `rails db:migrate` to update your database then try again.
```

そのため、ridgepole:apply と同様に rake task を定義しておくと良いと思います。
db/seeds.rb は ruby スクリプトで定義していることが多いので、そのまま移植するだけで済むことが多いと思います。

# production 環境への適用

手動で production 環境に migration していた場合は下記のコマンドを実行する。

```
$ bundle exec rails ridgepole:apply RAILS_ENV=production
```

CI/CD 連携している場合は、スクリプト等でこれまで ```bundle exec rails db:migrate``` をしていた箇所を ```bundle exec rails ridgepole:apply``` に差し替えれば良いと思います。

# ゴミ掃除

db/schema.rb や db/migrations 以下の migration ファイルを削除しておきましょう