1 ジェネレータとの最初の出会い
rails
コマンドでRailsアプリケーションを作成すると、実はRailsのジェネレータを利用したことになります。続いて、単にrails generate
と入力して実行すると、その時点でアプリケーションから利用可能なすべてのジェネレータのリストが表示されます。
$ rails new myapp $ cd myapp $ bin/rails generate
Railsで利用可能なすべてのジェネレータのリストが表示されます。たとえばヘルパージェネレータの詳細な説明が知りたい場合は以下のように入力します。
$ bin/rails generate helper --help
2 初めてジェネレータを作成する
Railsのジェネレータは、Rails 3.0以降はThorの上に構築されています。Thorは強力な解析オプションと優れたファイル操作APIを提供しています。具体例として、config/initializers
ディレクトリの下にinitializer.rb
という名前のイニシャライザファイルを1つ作成するジェネレータを構成してみましょう。
最初の手順として、以下の内容を持つlib/generators/initializer_generator.rb
というファイルを1つ作成します。
class InitializerGenerator < Rails::Generators::Base def create_initializer_file create_file "config/initializers/initializer.rb", "# Add initialization content here" end end
create_file
メソッドはThor::Actions
によって提供されています。create_file
およびその他のThorのメソッドのドキュメントについてはThorドキュメントを参照してください。
今作成した新しいジェネレータはきわめてシンプルです。Rails::Generators::Base
を継承しており、メソッド定義はひとつだけです。ジェネレータが起動されると、ジェネレータ内で定義されているパブリックメソッドが定義順に実行されます。最終的にcreate_file
メソッドが呼び出され、指定の内容を持つファイルが指定のディレクトリに1つ作成されます。RailsのアプリケーションテンプレートAPIを使い慣れている開発者であれば、すぐにも新しいジェネレータAPIに熟達できることでしょう。
以下を実行するだけで、この新しいジェネレータを呼び出すことができます。
$ bin/rails generate initializer
次に進む前に、今作成したばかりのジェネレータの説明を表示してみましょう。
$ bin/rails generate initializer --help
Railsでは、ジェネレータがActiveRecord::Generators::ModelGenerator
のように名前空間化されていれば実用的な説明文を生成できますが、この場合は残念ながらそのようになっていません。この問題は2とおりの方法で解決することができます。1つ目の方法は、ジェネレータ内でdesc
メソッドを呼び出すというものです。
class InitializerGenerator < Rails::Generators::Base desc "このジェネレータはconfig/initializersにイニシャライザファイルを作成する" def create_initializer_file create_file "config/initializers/initializer.rb", "# イニシャライザの内容をここに記述" end end
これで、--help
を付けて新しいジェネレータを呼び出すと新しい説明文が表示されるようになりました。説明文を追加する2番目の方法は、ジェネレータと同じディレクトリにUSAGE
という名前のファイルを作成することです。次に、この方法で実際に説明文を追加してみましょう。
3 ジェネレータを使用してジェネレータを生成する
Railsには、ジェネレータを生成するためのジェネレータもあります。
$ bin/rails generate generator initializer create lib/generators/initializer create lib/generators/initializer/initializer_generator.rb create lib/generators/initializer/USAGE create lib/generators/initializer/templates
上で作成したジェネレータの内容は以下のとおりです。
class InitializerGenerator < Rails::Generators::NamedBase source_root File.expand_path("../templates", __FILE__) end
上のジェネレータを見て最初に気付く点は、Rails::Generators::Base
ではなくRails::Generators::NamedBase
を継承していることでしょう。これは、このジェネレータを生成するためには少なくとも1つの引数が必要であることを意味します。この引数はイニシャライザの名前であり、コードではこのイニシャライザ名をname
という変数で参照できます。
新しいジェネレータを呼び出せば説明文が表示されます。なお、古いジェネレータファイルは必ず削除しておいてください。
$ bin/rails generate initializer --help Usage: rails generate initializer NAME [options]
新しいジェネレータにはsource_root
という名前のクラスメソッドも含まれています。このメソッドは、ジェネレータのテンプレートの置き場所を指定する場合に使用します。デフォルトでは、作成されたlib/generators/initializer/templates
ディレクトリを指します。
ジェネレータのテンプレートの機能を理解するために、lib/generators/initializer/templates/initializer.rb
を作成して以下の内容を追加してみましょう。
# 初期化内容をここに追記する
続いてジェネレータを変更し、呼び出されたときにこのテンプレートをコピーするようにします。
class InitializerGenerator < Rails::Generators::NamedBase source_root File.expand_path("../templates", __FILE__) def copy_initializer_file copy_file "initializer.rb", "config/initializers/#{file_name}.rb" end end
それではこのジェネレータを実行してみましょう。
$ bin/rails generate initializer core_extensions
config/initializers/core_extensions.rb
にcore_extensionsという名前のイニシャライザが作成され、そこにさっきのテンプレートが反映されていることが確認できます。copy_file
メソッドはコピー元のルートディレクトリから、指定のパスにファイルをひとつコピーしています。file_name
メソッドはRails::Generators::NamedBase
を継承したことで自動的に作成されます。
ジェネレータ関連で利用できるメソッドについては、本章の最終セクションで扱っています。
4 ジェネレータが参照するファイル
rails generate initializer core_extensions
を実行するとき、Railsは以下のファイルを上から順に見つかるまでrequireします。
rails/generators/initializer/initializer_generator.rb
generators/initializer/initializer_generator.rb
rails/generators/initializer_generator.rb
generators/initializer_generator.rb
どのファイルも見つからない場合はエラーメッセージが表示されます。
上の例ではアプリケーションのlib
ディレクトリの下にファイルを置いていますが、これらのディレクトリは$LOAD_PATH
に属していることがその理由です。
5 ワークフローをカスタマイズする
Rails自身が持つジェネレータはscaffoldを柔軟にカスタマイズできます。設定はconfig/application.rb
で行います。デフォルトのコードを以下にいくつか示します。
config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :test_unit, fixture: true end
ワークフローをカスタマイズする前のscaffoldは以下のように動作します。
$ bin/rails generate scaffold User name:string invoke active_record create db/migrate/20130924151154_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml invoke resource_route route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke test_unit create test/controllers/users_controller_test.rb invoke helper create app/helpers/users_helper.rb invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder invoke assets invoke coffee create app/assets/javascripts/users.js.coffee invoke scss create app/assets/stylesheets/users.css.scss invoke scss create app/assets/stylesheets/scaffolds.css.scss
この出力結果から、Rails 3.0以降のジェネレータの動作を容易に理解できます。実はscaffoldジェネレータ自身は何も生成していません。生成に必要なメソッドを順に呼び出しているだけです。このような仕組みになっているので、呼び出しを自由に追加/置換/削除できます。たとえば、scaffoldジェネレータはscaffold_controllerというジェネレータを呼び出しています。これはerbのジェネレータ、test_unitのジェネレータ、そしてヘルパーのジェネレータを呼び出します。ジェネレータごとに役割がひとつずつ割り当てられているので、コードを再利用しやすく、コードの重複も防げます。
最初のカスタマイズとして、ワークフローでスタイルシートとJavaScriptとテストフィクスチャファイルをscaffoldで生成しないようにしてみましょう。これは、以下のように設定を変更することで行うことができます。
config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false g.javascripts false end
scaffoldジェネレータでふたたびリソースを生成してみると、今度はスタイルシートとJavaScriptファイルとフィクスチャが生成されなくなります。ジェネレータをさらにカスタマイズしたい場合 (Active RecordとTestUnitをDataMapperとRSpecに置き換えるなど) は、必要なgemをアプリケーションに追加してジェネレータを設定するだけで済みます。
ジェネレータのカスタマイズ例を説明するために、ここで新しくヘルパージェネレータをひとつ作成してみましょう。このジェネレータはインスタンス変数を読み出すメソッドをいくつか追加するだけのシンプルなものです。最初に、Railsの名前空間の内側でジェネレータをひとつ作成します。名前空間の内側にする理由は、Railsはフックとして使用されるジェネレータを名前空間内で探索するからです。
$ bin/rails generate generator rails/my_helper create lib/generators/rails/my_helper create lib/generators/rails/my_helper/my_helper_generator.rb create lib/generators/rails/my_helper/USAGE create lib/generators/rails/my_helper/templates
続いて、templates
ディレクトリとsource_root
クラスメソッド呼び出しは使う予定がないのでジェネレータから削除します。ジェネレータにメソッドを追加して以下のようにしましょう。
# lib/generators/rails/my_helper/my_helper_generator.rb class Rails::MyHelperGenerator < Rails::Generators::NamedBase def create_helper_file create_file "app/helpers/#{file_name}_helper.rb", <<-FILE module #{class_name}Helper attr_reader :#{plural_name}, :#{plural_name.singularize} end FILE end end
新しく作ったジェネレータでproductsのヘルパーを実際に作成してみましょう。
$ bin/rails generate my_helper products create app/helpers/products_helper.rb
上を実行するとapp/helpers
に以下の内容を持つヘルパーが作成されます。
module ProductsHelper attr_reader :products, :product end
期待どおりの結果が得られました。上で生成したヘルパージェネレータをscaffoldで実際に使ってみるために、今度はconfig/application.rb
を編集して以下のように変更してみましょう。
config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false g.javascripts false g.helper :my_helper end
scaffoldを実行すると、ジェネレータの呼び出し時に以下のようになることが確認できます。
$ bin/rails generate scaffold Article body:text [...] invoke my_helper create app/helpers/articles_helper.rb
出力結果がRailsのデフォルトではなくなり、新しいヘルパーに従っていることがわかります。しかしここでもうひとつやっておかなければならないことがあります。新しいジェネレータにもテストを作成しておかなければなりません。そのために、元のヘルパーのテストジェネレータを再利用することにします。
Rails 3.0以降では「フック」という概念が利用できるので、このような再利用が簡単に行えます。今作ったヘルパーは特定のテストフレームワークのみに限定する必要はないため、ヘルパーがフックをひとつ提供し、テストフレームワークでそのフックを実装して互換性を得れば十分です。
これを実現するために、ジェネレータを以下のように変更しましょう。
# lib/generators/rails/my_helper/my_helper_generator.rb class Rails::MyHelperGenerator < Rails::Generators::NamedBase def create_helper_file create_file "app/helpers/#{file_name}_helper.rb", <<-FILE module #{class_name}Helper attr_reader :#{plural_name}, :#{plural_name.singularize} end FILE end hook_for :test_framework end
これで、ヘルパージェネレータが呼び出されてTestUnitがテストフレームワークとして設定されると、Rails::TestUnitGenerator
とTestUnit::MyHelperGenerator
を両方とも呼びだそうとします。しかしどちらも未定義なので、Railsのジェネレータとして実際に定義されているTestUnit::Generators::HelperGenerator
を代わりに呼び出すようジェネレータに指定することができます。具体的には、以下を追加するだけで済みます。
# :my_helperではなく:helperを探索する hook_for :test_framework, as: :helper
これでscaffoldを再実行すれば、作成されたリソースにテストも含まれているはずです。
6 ジェネレータのテンプレートを変更してワークフローをカスタマイズする
上でご紹介した手順では、生成されたヘルパーに一行追加しただけで、それ以外に何の機能も追加されていませんでした。同じことをもっと簡単に行う方法があります。それには、既存のジェネレータ (ここではRails::Generators::HelperGenerator
) のテンプレートを置き換えます。
Rails 3.0以降では、ジェネレータはソースルート・ディレクトリでテンプレートがあるかどうかを単に探すだけではなく、他のパスでもテンプレートを探します。lib/templates
ディレクトリもこの探索対象に含まれています。Rails::Generators::HelperGenerator
をカスタマイズするには、lib/templates/rails/helper
ディレクトリの中にhelper.rb
というテンプレートのコピーを作成します。このファイルを作成後、以下のコードを追加します。
module <%= class_name %>Helper attr_reader :<%= plural_name %>, :<%= plural_name.singularize %> end
次に、config/application.rb
の変更を元に戻します。
config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :test_unit, fixture: false g.stylesheets false g.javascripts false end
リソースをもう一度作成してみると、最初の手順のときとまったく同じ結果が得られます。この方法は、lib/templates/erb/scaffold
ディレクトリの下にedit.html.erb
やindex.html.erb
を作成することでscaffoldテンプレートやレイアウトをカスタマイズしたい場合に便利です。
RailsのscaffoldテンプレートではERBタグが多用されますが、これらが正常に生成されるためにはERBタグをエスケープしておく必要があります。
たとえば、テンプレートで以下のようなエスケープ済みERBタグが必要になることがあります (%
文字が1つ多い点にご注目ください)。
<%%= stylesheet_include_tag :application %>
上のコードから以下の出力が生成されます。
<%= stylesheet_include_tag :application %>
7 ジェネレータにフォールバックを追加する
最後にご紹介するジェネレータの機能はフォールバックです。これはプラグインのジェネレータを使用する場合に便利です。たとえば、TestUnitにshouldaのような機能を追加したいとします。TestUnitはRailsでrequireされるすべてのジェネレータで実装済みであり、shouldaではその一部を上書きするだけでよいはずです。このように、shouldaで実装する必要のないジェネレータの機能がいくつもあるので、RailsではShoulda
の名前空間で見つからないものについてはすべてTestUnit
ジェネレータのものを使用するように指定するだけでフォールバックを実現できます。
先に変更を加えたconfig/application.rb
にふたたび変更を加えることで、この動作を簡単にシミュレートできます。
config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :shoulda, fixture: false g.stylesheets false g.javascripts false # フォールバックを追加する g.fallbacks[:shoulda] = :test_unit end
これで、scaffoldでCommentを生成するとshouldaジェネレータが呼び出され、最終的にTestUnitジェネレータにフォールバックされるようになります。
$ bin/rails generate scaffold Comment body:text invoke active_record create db/migrate/20130924143118_create_comments.rb create app/models/comment.rb invoke shoulda create test/models/comment_test.rb create test/fixtures/comments.yml invoke resource_route route resources :comments invoke scaffold_controller create app/controllers/comments_controller.rb invoke erb create app/views/comments create app/views/comments/index.html.erb create app/views/comments/edit.html.erb create app/views/comments/show.html.erb create app/views/comments/new.html.erb create app/views/comments/_form.html.erb invoke shoulda create test/controllers/comments_controller_test.rb invoke my_helper create app/helpers/comments_helper.rb invoke jbuilder create app/views/comments/index.json.jbuilder create app/views/comments/show.json.jbuilder invoke assets invoke coffee create app/assets/javascripts/comments.js.coffee invoke scss
フォールバックを利用するとジェネレータの役割がひとつで済み、コードの重複を防いで再利用性を高めることができます。
8 アプリケーションテンプレート
ここまでで、Railsアプリケーション 内部 でのジェネレータの動作を解説しましたが、ジェネレータを使用して独自のRailsアプリケーション自身を生成することもできることをご存じでしょうか。このような目的で使用されるジェネレータは「アプリケーションテンプレート」と呼ばれます。ここではTemplates APIを簡単にご紹介します。詳細についてはRailsアプリケーションテンプレート入門を参照してください。
gem "rspec-rails", group: "test" gem "cucumber-rails", group: "test" if yes?("Would you like to install Devise?") gem "devise" generate "devise:install" model_name = ask("What would you like the user model to be called? [user]") model_name = "user" if model_name.blank? generate "devise", model_name end
上のテンプレートでは、Railsアプリケーションがrspec-rails
とcucumber-rails
gemに依存するように指定しています。この指定により、これらのgemはGemfile
のtest
グループに追加されます。続いて、Devise gemをインストールするかどうかをユーザーに問い合わせます。ユーザーが "y" または "yes" を入力するとGemfile
にDevise gemが追加され (特定のgemグループには含まれません)、devise:install
ジェネレータが実行されます。さらに続いてユーザー入力を受け付け、devise
のジェネレータにその入力結果を渡してジェネレータを実行します。
このテンプレートがtemplate.rb
という名前のファイルの中に含まれているとします。-m
オプションでテンプレートのファイル名を渡すことにより、rails new
コマンドの実行結果を変更することができます。
$ rails new thud -m template.rb
上のコマンドを実行するとThud
というアプリケーションが生成され、その結果にテンプレートが適用されます。
テンプレートの保存先はローカルでなくてもかまいません。-m
で指定するテンプレートの保存先としてオンライン上もサポートされています。
$ rails new thud -m https://gist.github.com/radar/722911/raw/
本章の最後のセクションでは、テンプレートで自由に使えるメソッドを多数紹介していますので、これを使用して自分好みのテンプレートを開発することができます。よく知られた素晴らしいアプリケーションテンプレートの数々を実際に生成する方法までは紹介しきれませんでしたが、何とぞご了承ください。これらのメソッドはジェネレータでも同じように使用できます。
9 ジェネレータメソッド
以下のメソッドはRailsのジェネレータとテンプレートのどちらでも同じように使用できます。
Thorが提供するメソッドについては本章では扱いません。Thorのドキュメントを参照してください。
9.1 gem
Railsアプリケーションのgem依存を指定します。
gem "rspec", group: "test", version: "2.1.0" gem "devise", "1.1.5"
以下のオプションを利用できます。
-
:group
- gemを追加するGemfile
内のグループを指定します。 -
:version
- 使用するgemのバージョンを指定します。version
オプションを明記せずに、メソッドの第2引数としてバージョンを指定することもできます。 -
:git
- gemが置かれているgitリポジトリを指すURLを指定します。
メソッドでこれら以外のオプションも使用する場合は、以下のように行の最後に記述します。
gem "devise", git: "git://github.com/plataformatec/devise", branch: "master"
上のコードが実行されると、Gemfile
に以下の行が追加されます。
gem "devise", git: "git://github.com/plataformatec/devise", branch: "master"
9.2 gem_group
gemのエントリを指定のグループに含めます。
gem_group :development, :test do gem "rspec-rails" end
9.3 add_source
指定のソースをGemfile
に追加します。
add_source "http://gems.github.com"
9.4 inject_into_file
ファイル内の指定の場所にコードブロックをひとつ挿入します。
inject_into_file 'name_of_file.rb', after: "#挿入したいコードを次の行に置く。最後のend\nの後ろには必ず改行を入れること。" do <<-'RUBY' puts "Hello World" RUBY end
9.5 gsub_file
ファイル内のテキストを置き換えます。
gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'
正規表現を使用して置き換え方法を精密に指定できます。append_file
を使用してコードをファイルの末尾に追加したり、prepend_file
を使用してコードをファイルの冒頭に挿入したりすることもできます。
9.6 application
config/application.rb
ファイル内でアプリケーションクラス定義の直後に指定の行を追加します。
application "config.asset_host = 'http://example.com'"
このメソッドにはブロックを渡すこともできます。
application do "config.asset_host = 'http://example.com'" end
以下のオプションを利用できます。
-
:env
- 設定オプションの環境を指定します。ブロック構文を使用する場合は以下のようにすることが推奨されます。
application(nil, env: "development") do "config.asset_host = 'http://localhost:3000'" end
9.7 git
gitコマンドを実行します。
git :init git add: "." git commit: "-m First commit!" git add: "onefile.rb", rm: "badfile.cxx"
引数またはオプションとなるハッシュの値は、指定のgitコマンドに渡されます。上の最後の行で示しているように、一行に複数のgitコマンドを記述することができますが、この場合コマンドの実行順序は記載順になるとは限らないので注意が必要です。
9.8 vendor
指定のコードを含むファイルをvendor
ディレクトリに置きます。
vendor "sekrit.rb", '#極秘
このメソッドにはブロックをひとつ渡すこともできます。
vendor "seeds.rb" do "puts 'in your app, seeding your database'" end
9.9 lib
指定のコードを含むファイルをlib
ディレクトリに置きます。
lib "special.rb", "p Rails.root"
このメソッドにはブロックをひとつ渡すこともできます。
lib "super_special.rb" do puts "Super special!" end
9.10 rakefile
Railsアプリケーションのlib/tasks
ディレクトリにRakeファイルをひとつ作成します。
rakefile "test.rake", "hello there"
このメソッドにはブロックをひとつ渡すこともできます。
rakefile "test.rake" do %Q{ task rock: :environment do puts "Rockin'" end } end
9.11 initializer
Railsアプリケーションのlib/initializers
ディレクトリにイニシャライザファイルをひとつ作成します。
initializer "begin.rb", "puts 'ここが最初の部分'"
このメソッドにはブロックをひとつ渡すこともでき、文字列が返されます。
initializer "begin.rb" do "puts 'this is the beginning'" end
9.12 generate
指定のジェネレータを実行します。第1引数は実行するジェネレータ名で、残りの引数はジェネレータにそのまま渡されます。
generate "scaffold", "forums title:string description:text"
9.13 rake
Rakeタスクを実行します。
rake "db:migrate"
以下のオプションを利用できます。
-
:env
- rakeタスクを実行するときの環境を指定します。 -
:sudo
- rakeタスクでsudo
を使用するかどうかを指定します。デフォルトはfalse
です。
9.14 capify!
Capistranoのcapify
コマンドをアプリケーションのルートディレクトリで実行し、Capistranoの設定を生成します。
capify!
9.15 route
config/routes.rb
ファイルにテキストを追加します。
route "resources :people"
9.16 readme
テンプレートのsource_path
にあるファイルの内容を出力します。通常このファイルはREADMEです。
readme "README"