Rails 5.2のマイグレーションファイルをSquasherで集約してみた

# 前置き
モバイルアプリのバックエンドを学習を兼ねてRailsで開発しています。「マイグレーションファイルを統合出来ないかな」と思っていたところ、[こちらの書籍](https://www.amazon.co.jp/dp/B07JHQ9B5T)にてSquasherが紹介されていたので、試してみました。

# まずは現状を確認する
dbディレクトリに以下のマイグレーションファイルがあります。

```
$ tree
.
├── migrate
│   ├── 20181129152052_create_users.rb
│   ├── 20181129152325_add_column_to_users.rb
│   ├── 20181230120205_rename_column_device_token.rb
│   ├── 20181231084154_add_index_to_user.rb
│   ├── 20181231142632_create_devices.rb
│   ├── 20181231151529_add_reference_to_users.rb
│   ├── 20181231151946_add_reference_to_devices.rb
│   ├── 20190101175529_create_rooms.rb
│   ├── 20190101182417_create_talks.rb
│   ├── 20190101183525_add_reference_to_talks.rb
│   ├── 20190101183740_add_reference_to_rooms.rb
│   ├── 20190101184243_add_reference_talk_to_users.rb
│   ├── 20190101184438_add_reference_user_to_talks.rb
│   ├── 20190102101406_create_user_room_mappings.rb
│   ├── 20190102102554_add_index_to_user_room_mappings.rb
│   ├── 20190102133103_add_column_status_to_user_rooms_mappings.rb
│   ├── 20190102134153_rename_table_user_rooms_mappings_to_residence.rb
│   ├── 20190103102845_rename_table_residence_to_residences.rb
│   ├── 20190103140215_add_reference_rooms_to_user.rb
│   ├── 20190103140817_add_reference_users_to_room.rb
│   ├── 20190103144721_rename_column_user_id_to_users.rb
│   ├── 20190109141322_rename_column_capacity_posts_par_user_to_rooms.rb
│   ├── 20190119135319_rename_column_capacity_users_to_rooms.rb
│   ├── 20190119135359_rename_column_capacity_post_per_user_to_rooms.rb
│   ├── 20190119143121_add_column_lifetime_to_rooms.rb
│   ├── 20190119143654_del_column_name_to_rooms.rb
│   ├── 20190119145901_del_reference_from_users.rb
│   └── 20190119150920_del_reference_to_rooms.rb
├── schema.rb
└── seeds.rb
```

# インストールする
基本的には[公式](https://github.com/jalkoby/squasher)に沿って進めていきます。

1. GemfileにSquasherを追記します

    ```Ruby:Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

    ruby '2.5.1'
gem 'rails', '~> 5.2.2'
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'config', '~> 1.7.0'
gem 'jwt', '~> 2.1.0'

    group :development, :test do
      gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
      gem 'rspec-rails'
      gem 'factory_bot_rails'
end

    group :development do
      gem 'listen', '>= 3.0.5', '< 3.2'
      gem 'spring'
      gem 'spring-watcher-listen', '~> 2.0.0'
      gem 'rubocop', '~> 0.60.0', require: false
end

    # --- Squasherに関するGemを追加 ここから
    group :tools do
      gem 'squasher', '>= 0.6.0'
      gem 'capistrano'
      # gem 'rubocop'  当プロジェクトでは既に導入しているので除外
end
    # --- Squasherに関するGemを追加 ここまで

    gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
```
1. Gemをインストールします

    ```
# bundle install
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
        :
Fetching squasher 0.6.2
Installing squasher 0.6.2
Bundle complete! 16 Gemfile dependencies, 87 gems now installed.
Bundled gems are installed into `./vendor/bundle`
```
1. Squacherを導入します
 
    ```
# bundle binstub squasher
# bin/squasher
Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.
```
環境の違いからか`bundle binstubs bundler --force`するよう求められました。無事に利用可能になりました:thumbsup:

    ```
# bundle binstubs bundler --force
# bin/squasher
Squasher 0.6.2
```

# 実行する
1. squasherコマンドの実行します  
「○○より前のマイグレーションファイルを集約する」という指示の仕方になります。今までのマイグレーションファイルを全て集約することにしますので2020年を指定します。Rails 5.2を利用しているのでmオプションの引数に5.2を指定します。

    ```
# bin/squasher 2020 -m 5.2
Squasher is creating a tmp database
Dropped database 'squasher'
Created database 'squasher'
Squasher is applying migrations on a tmp database
== 20181129152052 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0317s
== 20181129152052 CreateUsers: migrated (0.0319s) =============================
        :
== 20190119150920 DelReferenceToRooms: migrating ==============================
-- remove_reference(:rooms, :talk, {:foreign_key=>true})
   -> 0.0932s
== 20190119150920 DelReferenceToRooms: migrated (0.0934s) =====================
```
1. SquasherのDBの削除やクリーンの可否を選択します

    ```
Squasher's created the `squasher` database for its needs.
It might be useful to keep it if any of your deleted migrations inserts data or
you squash migrations in a few steps (look at -r option).
Keep it (yes / no)?
yes     # デフォルト
```

    ```
Do you want to clean your database from the old schema migration records(yes/no)?
no      # デフォルト
```

# 実行後のマイグレーションファイルを確認する
集約されたことがわかります。

```
$ tree
.
├── migrate
│   └── 20190119150920_init_schema.rb
├── schema.rb
└── seeds.rb
```

# 再構築してみる
集約されたマイグレーションファイルでDBを再構築してみます。

```bash
$ bundle exec rails db:drop
Dropped database 'app_development'
Dropped database 'app_test'

$ bundle exec rails db:create
Created database 'app_development'
Created database 'app_test'

$ bundle exec rails db:migrate
== 20190119150920 InitSchema: migrating =======================================
-- create_table("devices", {:options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8"})
   -> 0.0399s
    :
-- add_foreign_key("talks", "users")
   -> 0.0749s
== 20190119150920 InitSchema: migrated (0.4857s) ==============================
```

# 補足(失敗談)
Rails5.2では公式の手順をコピペしてmオプションに5.0を指定すると再構築でエラーが発生します。

```
# bin/squasher 2020 -m 5.0
```

Rails5.0ではデフォルトで作成されるidカラムはint型で、Rails5.1以降で作成されるidカラムはbigint型です。Rails5.2で作成された主キーと外部キーの関係はbigint型が前提となっているので、このようなエラーが発生します。

```
$ bundle exec rails db:migrate
== 20190119150920 InitSchema: migrating =======================================
-- adapter_name()
   -> 0.0000s
    :
-- add_foreign_key("devices", "users")
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

Column `user_id` on table `devices` has a type of `bigint(20)`.
This does not match column `id` on `users`, which has type `int(11)`.
To resolve this issue, change the type of the `user_id` column on `devices` to be :integer. (For example `t.integer user_id`).
    :
bin/rails:4:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
```

mオプションで継承するActiveRecord::Migrationのバージョンが変わるようですね。

|mオプション|継承元|
|---|---|
|5.0|class InitSchema < ActiveRecord::Migration[5.0]|
|5.2|class InitSchema < ActiveRecord::Migration[5.2]|

# 終わりに
先述の失敗談で少しハマりましたが、目的を達成することは出来ました。特にカラム名のtypoを修正するマイグレーションファイルなどは集約されスッキリしてよかったです。