migrateを辞めてridgepoleを導入する

# 導入のきっかけ

どれだけ緻密にテーブル設計をしても、途中仕様変更や追加要望が入って、初期の見積もりから大幅にテーブル構造が変更されることは目に見えてる。
Railsの場合、スキーマを変更するにはスキーマファイルを追加していかないといけない。
でもスキーマの変更履歴は `git` で追えるし、ぶっちゃけ最新のスキーマが見られればそれで十分なんだよね🤔

ということで無駄にスキーマファイルが増えるのを嫌い、ridgepole導入を決意しました。
めちゃ簡単だよ🙆‍♂️

# 動作環境
`ruby 3.0.2`
`Rails 6.1.4`
`MySQL 8.0.25`

# 導入手順

## 1. ridgepoleをインストール

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

```shell:terminal
% bundle install
```

Gemfileに `rigepole` を追記し `bundle install`。
これで面倒な `migrate` ではなく `ridgepole` が使用できるようになりました。はやっ。

## 2. 試しにauthorsテーブルを作ってみる

`ridgepole`に関わるファイルは1つの階層に纏めておきたいので、dbディレクトリ配下に`schemas`ディレクトリを作成します。
（自分が分かりやすければ、名前はschemasじゃなくてもOK）
次に `Schemafile` という名前のファイルをschemasディレクトリに作成。
そして同じ階層に`authors`テーブルのスキーマを定義したスキーマファイルを作成します。

```shell:terminal
% mkdir -p db/schemas
% touch db/schemas/Schemafile
% touch db/schemas/authors.schema.rb
```

`authors`テーブルのスキーマは仮に下記のように定義しておくとしましょう。

```ruby:db/schemas/authors.schema.rb
create_table :authors, force: :cascade, charset: 'utf8mb4', collation: 'utf8mb4_0900_ai_ci', options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC' do |t|
  t.string  :name, null: false, default: ''
  t.integer :age,  null: false, default: 20

  t.index :name, name: 'index_authors_on_name'
end
```

`authors`テーブルのスキーマファイルが出来たら、それを `Schemafile` で読み込んで。

```ruby:Schemafile
require 'authors.schema.rb'
```

そして最後に`ridgepole`コマンドを実行！

```shell:terminal
% bundle exec ridgepole --config config/database.yml --env development --file db/schemas/Schemafile --apply
```

やや長ったらしいコマンドで分かりづらいので、少しだけ`ridgepole`コマンドのオプションに関して説明します☝️
[※ 詳しくは公式ドキュメントのHelp参照](https://github.com/ridgepole/ridgepole#help)

|オプション|省略形|引数|役割|
|:-|:-|:-|:-|
|--config |-c|config/database.yml|DBに関するconfigファイルの指定|
|--env|-E|development, test, production|実行環境の指定|
|--file |-f|db/schemas/Schemafile|スキーマファイルの指定|
|--apply|-a|なし|apply（実行）|

つまり先程のコマンドは、

```
・ DBに関するconfigファイルは config/database.yml
・ 実行環境は development
・ スキーマファイルは db/schemas/Schemafile
```

以上の設定で`ridgepole`コマンドをapply（実行）します、という意味でした🤗
意外とシンプル笑

さて、本題に戻ります↩️
先程のコマンドを実行すると、下記のような実行結果が表示されてると思います。

```shell:terminal
Apply `db/schemas/Schemafile`
-- create_table("authors", {:charset=>"utf8mb4", :collation=>"utf8mb4_0900_ai_ci", :options=>"ENGINE=InnoDB ROW_FORMAT=DYNAMIC"})
   -> 0.0501s
-- add_index("authors", ["name"], {:name=>"index_authors_on_name"})
   -> 0.0101s
```

表示結果を見る限り、`authors`テーブルが作られ`index`も貼られているようです。
では実際にDBにログインしてテーブルが正しく作成されているか確認しましょう。

```shell:terminal
% mysql db名
```

ログイン出来たらテーブル一覧を表示し、`authors`テーブルがあればテーブル構造を確認します。

```sql:mysql
mysql> show tables;
+-------------------------------+
| Tables_in_db_name             |
+-------------------------------+
| ar_internal_metadata          |
| authors                       |
| schema_migrations             |
+-------------------------------+
3 rows in set (0.00 sec)

mysql> desc authors;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | bigint       | NO   | PRI | NULL    | auto_increment |
| name  | varchar(255) | NO   | MUL |         |                |
| age   | int          | NO   |     | 20      |                |
+-------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
```

うんうん、無事に作られていたようですね！とても簡単◎

## 3. スキーマを変更したい場合

先程の`authors`テーブルですが、実はとっても大切なカラムを忘れていました。
そう、**`timestamps`**です。

`timestamps`とは、どのテーブルにも当たり前のようにある`created_at`と`updated_at`のことです。
レコードの作成日時と更新日時を保持する大事なカラムですね。

Railsデフォルトの`migrate`を使用していた場合、`rails g migrate 〜`コマンドでスキーマファイルを追加する必要がありましたが、`ridgepole`は違います。
なんと先程作った`authors`スキーマを書き換え、再度`ridgepole`コマンドを実行するだけでいいのです！簡単✨

ということで早速編集。

```diff:db/schemas/authors.schema.rb
create_table :authors, force: :cascade, charset: 'utf8mb4', collation: 'utf8mb4_0900_ai_ci', options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC' do |t|
  t.string  :name, null: false, default: ''
  t.integer :age,  null: false, default: 20
+ 
+ t.timestamps

  t.index :name, name: 'index_authors_on_name'
end
```

そして`ridgepole`コマンド実行！

```shell:terminal
% bundle exec ridgepole --config config/database.yml -env development --file db/schemas/Schemafile --apply
```

以下、実行結果。

```shell:terminal
Apply `db/schemas/Schemafile`
-- add_column("authors", "created_at", :datetime, {:null=>false, :after=>"age"})
   -> 0.0169s
-- add_column("authors", "updated_at", :datetime, {:null=>false, :after=>"created_at"})
   -> 0.0145s
```

うん、無事に追加出来たようですね！
先程の手順と同様、DBにログインしてテーブル構造を確認してみます。

```shell:terminal
% mysql db名
```

```sql:mysql
mysql> desc authors;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint       | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | NO   | MUL |         |                |
| age        | int          | NO   |     | 20      |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
```

しっかり追加されていました！万歳🙌

## 4. おまけ

最後に`ridgepole`コマンドを短くする方法を紹介します。
これをすると下記のように短いコマンドで同じことを出来るようになります。

```shell:terminal
# 通常のridgepoleコマンド
% bundle exec ridgepole --config config/database.yml -env development --file db/schemas/Schemafile --apply

# rakeタスクを使ったridgepoleコマンド
% bundle exec rails ridgepole:apply
```

では実際の作業手順を説明していきます。

`ridgepole`はとても便利だけどコマンドが少々長く覚えづらいのがデメリットです。
しかもconfigファイルやスキーマファイルを毎回コマンド実行のたびに指定しなきゃいけないのはちょっと無駄な気がする。。。

しかし、こんなデメリットは`rakeタスク`を作ると解消できますよ🙆‍♂️
今回は`ridgepole`という名前のタスクにします。

```shell:terminal
% bundle exec rails g task ridgepole
```

すると`lib/tasks`ディレクトリの配下に`ridgepole.rake`というファイルが作成されます。
このファイルを以下のように編集してください。

```ruby:lib/tasks/ridgepole.rake
namespace :ridgepole do
  desc 'Apply schema to database'
  task apply: :environment do
    run('--apply')
  end

  private

    def run(*options)
      config_file = 'config/database.yml'
      schema_file = 'db/schemas/Schemafile'
      rails_env   = ENV['RAILS_ENV'] || Rails.env

      base_command = "bundle exec ridgepole --config #{config_file} --env #{rails_env} --file #{schema_file}"
      full_command = [base_command, *options].join(' ')

      puts '=== run ridgepole... ==='
      puts "[Running] #{full_command}"

      system full_command
    end
end
```

これで`rakeタスク`を作ることが出来ました！簡単でしょ？
ちなみに`ridgepole`コマンドのオプションを省略形で書かないのは、誰が見ても分かるように（もしくは分かりやすく）するため。
今後`ridgepole`コマンドを実行したいときは下記のコマンドで実行できます🎉

```shell:terminal
% bundle exec rails ridgepole:apply
```

## 5. さらにおまけ

`ridgepole`はスキーマをDBに反映させる以外に、DBの内容を`Schemafile`に反映することもできます。
その機能も使う場合には`rakeタスク`を下記のように変更しましょう。

```ruby:lib/tasks/ridgepole.rake
namespace :ridgepole do
  desc 'Apply schema to database'
  task apply: :environment do
    run('--apply', '--file')
  end

  desc 'Export schema to Schemafile'
  task export: :environment do
    run('--export', '--split', '--output')
  end

  private

    def run(*options)
      config_file = 'config/database.yml'
      schema_file = 'db/schemas/Schemafile'
      rails_env   = ENV['RAILS_ENV'] || Rails.env

      base_command = "bundle exec ridgepole --config #{config_file} --env #{rails_env}"
      full_command = [base_command, *options, schema_file].join(' ')

      puts '=== run ridgepole... ==='
      puts "[Running] #{full_command}"

      system full_command
    end
end
```

使い方は以下の通り。

```shell:terminal
% bundle exec ridgepole:export
```

***Enjoy ridgepole's life*** 👍

# 参考記事
[Ridgepole - Github](https://github.com/ridgepole/ridgepole)
[Rakeタスクの実装](https://qiita.com/yoshito410kam/items/26c3c6e519d4990ed739)