devise gemやpaper_trail gemでやっているような、migrationファイルをテンプレートから自動生成するgemの作り方。
前提知識1:Railsのジェネレーター
migrationファイルの生成は、Railsのジェネレーターという機能を使って実装されており、最終的にはrails generate your_gem:install
というコマンドでmigrationファイルを生成することになる。概念的な話と基本的な機能はRailsのチュートリアルを読むと分かりやすい。
前提知識2:アプリケーションテンプレート
Railsのジェネレーターの通常の使い方はgenerate model user name:string
のように自分が作っているRailsアプリケーションにファイルを追加すること。
それをさらに汎用的にしたのがアプリケーションテンプレートと呼ばれる仕組みであり、その中でも、generate your_gem:install
については、所定のディレクトリにあらかじめ決められたファイルを置くだけで実現できる。
# まずは自分のgemを作る必要がある
bundle gem your_gem
所定のディレクトリに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ファイルのテンプレートの書き方は下記の通り。
class CreateUsers < ActiveRecord::Migration<%= migration_version %>
def change
create_table :users do |t|
t.string :name
end
end
end
上記の2ファイルを作れば、それだけでmigrationファイルの生成が実行できる。
# 作ったgemをインストールする
gem install 'your_gem'
# migrationファイルのコピーを実行する
rails generate your_gem:install
実用的にするための工夫
上記の手順だけで基本的なmigrationファイルの生成はできるが、実用上は下記の問題が起きる。
- migrationファイルのバージョン番号を生成する必要がある(例:20181217174759_create_users)
- migrationファイルの書き方はRailsのバージョンによって異なっている
- 利用するRDBによって使える機能に差がある
それぞれの解決策の例は下記の通り。
# migrationファイルのバージョン生成
ActiveRecord::Generators::Base.next_migration_number(dirname)
# Railsのバージョンに応じたmigrationファイルの書き分け
def migration_version
major = ActiveRecord::VERSION::MAJOR
if major >= 5
"[#{major}.#{ActiveRecord::VERSION::MINOR}]"
end
end
# 使っている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
paper_trail / install_generator.rb
rails / migration_generator.rb