migrationファイルを生成するgenerator（rails g your_gem:install）の作り方

[devise gem](https://github.com/plataformatec/devise)や[paper_trail gem](https://github.com/paper-trail-gem/paper_trail)でやっているような、migrationファイルをテンプレートから自動生成するgemの作り方。

## 前提知識1：Railsのジェネレーター

migrationファイルの生成は、Railsのジェネレーターという機能を使って実装されており、最終的には`rails generate your_gem:install`というコマンドでmigrationファイルを生成することになる。概念的な話と基本的な機能はRailsのチュートリアルを読むと分かりやすい。

[Rails ジェネレータとテンプレート入門](https://railsguides.jp/generators.html)

## 前提知識2：アプリケーションテンプレート

Railsのジェネレーターの通常の使い方は`generate model user name:string`のように自分が作っているRailsアプリケーションにファイルを追加すること。

それをさらに汎用的にしたのがアプリケーションテンプレートと呼ばれる仕組みであり、その中でも、`generate your_gem:install`については、所定のディレクトリにあらかじめ決められたファイルを置くだけで実現できる。

```shell
# まずは自分のgemを作る必要がある
bundle gem your_gem
```

所定のディレクトリに`install_generator.rb`を置く。

```ruby:your_gem/lib/generators/your_gem/install/install_generator.rb

module YourGem
  class InstallGenerator < ::Rails::Generators::Base
    include ::Rails::Generators::Migration

    source_root File.expand_path('templates', __dir__)

    # メソッド名は何でもよい。インスタンスメソッドが上から順番に実行される
    def create_migration_file
      template = 'create_users'
      migration_dir = File.expand_path("db/migrate")

      if self.class.migration_exists?(migration_dir, template)
        ::Kernel.warn "Migration already exists: #{template}"
      else
        migration_template(
            "#{template}.rb.erb",
            "db/migrate/#{template}.rb",
            migration_version: migration_version
        )
      end
    end

    private

    def migration_version
      major = ActiveRecord::VERSION::MAJOR
      if major >= 5
        "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
      end
    end
  end
end
```

migrationファイルのテンプレートの書き方は下記の通り。

```ruby:your_gem/lib/generators/your_gem/install/templates/create_users.rb.erb

class CreateUsers < ActiveRecord::Migration<%= migration_version %>
  def change
    create_table :users do |t|
      t.string   :name
    end
  end
end
```

上記の2ファイルを作れば、それだけでmigrationファイルの生成が実行できる。

```shell
# 作ったgemをインストールする
gem install 'your_gem'

# migrationファイルのコピーを実行する
rails generate your_gem:install
```

## 実用的にするための工夫

上記の手順だけで基本的なmigrationファイルの生成はできるが、実用上は下記の問題が起きる。

- migrationファイルのバージョン番号を生成する必要がある（例：20181217174759_create_users）
- migrationファイルの書き方はRailsのバージョンによって異なっている
- 利用するRDBによって使える機能に差がある

それぞれの解決策の例は下記の通り。

```ruby
# migrationファイルのバージョン生成
ActiveRecord::Generators::Base.next_migration_number(dirname)
```

```ruby
# Railsのバージョンに応じたmigrationファイルの書き分け
def migration_version
  major = ActiveRecord::VERSION::MAJOR
  if major >= 5
    "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
  end
end
```

```ruby
# 使っているRDBの判別
def mysql?
  MYSQL_ADAPTERS.include?(ActiveRecord::Base.connection.class.name)
end

# RDBごとのCREATE TABLEのオプションの書き分け
def users_table_options
  if mysql?
    ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
  else
    ""
  end
end
```

実際にはさらに丁寧なバージョンのチェック、細かいif文が必要であり、ここには詳細を書ききれない。この辺は必要に応じて実際のgemを見た方が分かりやすい。

[devise / devise_generator.rb](https://github.com/plataformatec/devise/blob/master/lib/generators/active_record/devise_generator.rb)
[paper_trail / install_generator.rb](https://github.com/paper-trail-gem/paper_trail/blob/master/lib/generators/paper_trail/install/install_generator.rb)
[rails / migration_generator.rb](https://github.com/rails/rails/blob/7bc343b3a9ef7581e665a9f70f00d494f7a3b62c/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb)