rails でカスタム generator 作る話
これは何か
好きな generator を作る話。ここで述べる generator
とは
rails generator hoge
のような rails generate
コマンドのこと。
知識整理のために書いてみる
雛形作成
rails には generator を生成する generator があって、以下の様なコマンドを叩けば良い。
rails generate generator <generator名>
試しに test
という generator を作ってみる。
% rails generate generator test create lib/generators/test create lib/generators/test/test_generator.rb create lib/generators/test/USAGE create lib/generators/test/templates invoke test_unit create test/lib/generators/test_generator_test.rb
ファイルが作られた。 作られた generator は以下のように実行できる
rails generate <generator名> (<引数1>, <引数2>, ...)
引数は generator 側の定義で制御する
generator 作成
生成されたファイル
先ほどの rails generate generator test
コマンドで生成されたファイルを順番に見ていく
lib/generators/test/test_generator.rb
class TestGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) end
メインの generator ファイル。
Rails::Generators::NamedBase
を継承した {generator名}Generator
というクラスが生成されている。
ここにメソッドを定義していく。
具体例
TestGenerator
に何も生産しないメソッドを作る。
class TestGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) def ore puts "俺が" end def dhh puts "DHH、こと" end def da puts "David Heinemeier Hanssonだ!" end end
rails generate
コマンドを叩くと TestGenerator
クラスのインスタンスメソッドが 上から全部実行される
% rails generate test test # test という引数を仮に与えている 俺が DHH、こと David Heinemeier Hanssonだ!
ご覧のとおり上から実行されていることがわかる。
なのでファイルを作りたかったら、そういうメソッドを書けばよい。
ディレクトリを掘りたかったら、そういうメソッドを書けば良い。簡単
lib/generators/test/USAGE
中身は以下の通り。
Description: Explain the generator Example: rails generate test Thing This will create: what/will/it/create
ヘルプコマンドの下の方にそのまま使われる
% rails generate test -h Usage: rails generate test NAME [options] Options: [--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated applications) Runtime options: -f, [--force] # Overwrite files that already exist -p, [--pretend], [--no-pretend] # Run but do not make any changes -q, [--quiet], [--no-quiet] # Suppress status output -s, [--skip], [--no-skip] # Skip files that already exist Description: Explain the generator Example: rails generate test Thing This will create: what/will/it/create
lib/generators/test/templates/
ディレクトリ。現在は空。 erb テンプレートファイルを置いておく用のディレクトリ
test/lib/generators/test_generator_test.rb
require 'test_helper' require 'generators/test/test_generator' class TestGeneratorTest < Rails::Generators::TestCase tests TestGenerator destination Rails.root.join('tmp/generators') setup :prepare_destination # test "generator runs without errors" do # assert_nothing_raised do # run_generator ["arguments"] # end # end end
テストの雛形。rspec 使ってれば rspec で出してくれる。
...と思いきや、rspec の場合はスケルトン作成に失敗する。
(失敗の原因を詳しく追ってない。本当は作られるはず... なのかな)
継承クラス
親クラスは若干注意が必要なので、ここでやりたいことに合わせて分岐する
Rails::Generators::NamedBase
default だとこっち。
rails generate
コマンドの引数として、一つ何かを受け取ることが必須のクラス
cf. Rails::Generators::NamedBase
% rails generate controller Users xxxx
のような馴染みのある generator を作りたい場合はこちらで良い。
Rails::Generators::Base
NamedBase とは異なり rails generate
コマンドの引数を自由に定義できるクラス。
Rails::Generators::NamedBase
は Rails::Generators::Base
を継承している。
こちらのほうが柔軟性があるが、特別な用途がない限りは使わなくても良いかと思います。
便利メソッド
generator なので、テンプレートからファイル生成したり、ディレクトリを掘ったりする用途が主である。
rails はすごいから、そういう簡単なものは既にメソッドが用意してある。
これらのメソッドは全て Thor::Actions
に定義されているので、そっちを見たほうがいい。
というのも Rails::Generators::Base
が Thor::Actions
を include しているから。
もっと言うと Rails::Generators::Base
は Thor::Group
を継承して作られているので、rails の generator は大体 thor でできてると言って良い。
したがって困ったときは thor で検索すると良い
以下は便利メソッドの内、いくつかを紹介
create_file
directory
directory を copy する
template
ERB テンプレートを使って、ファイルを生成
具体例
class TestGenerator < Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) def create_empty_file create_file "aaaa.txt" end end
% rails g test arg1
create aaaa.txt
見たことある出力やー
% rails g test arg1
identical Test.txt
もう一回やると、またまた見たことある出力に。
大体こんな感じでノリで generator 作れてしまいます。すごい簡単です。
まとめ
- 継承するクラスによって、generator に渡せる引数が変わる
Rails::Generators::NamedBase
は 1 つの引数を受け取るRails::Generators::Base
は自在に定義可能
rails generator
の内部は殆どthor
- 詰まったら
thor
で調べると良い thor
を一度触ったことがある人は知見を十分に活かせるthor
はとても素直な実装になっているからthor
の spec はとてもわかり易いので、読むだけで理解できる
- 詰まったら
hook_for
は謎が多いので、使う時は若干注意thor
は「トール」と読むらしい