Rails ジェネレータとテンプレート入門

Railsの各種ジェネレータは、ワークフローを改善するために欠かせないツールです。本ガイドは、Railsジェネレータの作成方法および既存のジェネレータのカスタマイズ方法について解説します。

このガイドの内容:

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
      invoke  test_unit
      create    test/lib/generators/initializer_generator_test.rb

上で作成したジェネレータの内容は以下のとおりです。

class InitializerGenerator < Rails::Generators::NamedBase
  source_root File.expand_path('templates', __dir__)
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', __dir__)

  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  test_unit
      create    test/application_system_test_case.rb
      create    test/system/users_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.coffee
      invoke    scss
      create      app/assets/stylesheets/users.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

この出力結果から、Rails 3.0以降のジェネレータの動作を容易に理解できます。実はscaffoldジェネレータ自身は何も生成していません。生成に必要なメソッドを順に呼び出しているだけです。このような仕組みになっているので、呼び出しを自由に追加/置換/削除できます。たとえば、scaffoldジェネレータはscaffold_controllerというジェネレータを呼び出しています。これはerbのジェネレータ、test_unitのジェネレータ、そしてヘルパーのジェネレータを呼び出します。ジェネレータごとに役割がひとつずつ割り当てられているので、コードを再利用しやすく、コードの重複も防げます。

scaffoldでのリソース生成時にデフォルトのapp/assets/stylesheets/scaffolds.scssファイルの生成を回避したい場合は、scaffold_stylesheetで無効にできます。

  config.generators do |g|
    g.scaffold_stylesheet false
  end

これでワークフローの次回のカスタマイズでスタイルシートの生成が行われなくなります。JavaScriptファイルやテストのfixtureファイルの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
      invoke  test_unit
      create    test/lib/generators/rails/my_helper_generator_test.rb

続いて、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::TestUnitGeneratorTestUnit::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.erbindex.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  test_unit
      create    test/application_system_test_case.rb
      create    test/system/comments_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.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-railscucumber-rails gemに依存するように指定しています。この指定により、これらのgemはGemfiletestグループに追加されます。続いて、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を利用しています。

class_option :scope, type: :string, default: 'read_products'

これで、ジェネレータを以下のように呼び出せます。

rails generate initializer --scope write_products

このコマンドライン引数は、ジェネレータクラス内ではoptionsメソッドでアクセスできます。

@scope = options['scope']

10 ジェネレータメソッド

以下のメソッドはRailsのジェネレータとテンプレートのどちらでも同じように使用できます。

Thorが提供するメソッドについては本章では扱いません。Thorのドキュメントを参照してください。

10.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: "https://github.com/plataformatec/devise.git", branch: "master"

上のコードが実行されると、Gemfileに以下の行が追加されます。

gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master"

10.2 gem_group

gemのエントリを指定のグループに含めます。

gem_group :development, :test do
  gem "rspec-rails"
end

10.3 add_source

指定のソースをGemfileに追加します。

add_source "http://gems.github.com"

このメソッドもブロックを1つ取ります。

add_source "http://gems.github.com" do
  gem "rspec-rails"
end

10.4 inject_into_file

ファイル内の指定の場所にコードブロックをひとつ挿入します。

inject_into_file 'name_of_file.rb', after: "#挿入したいコードを次の行に置く。最後のend\nの後ろには必ず改行を入れること。" do <<-'RUBY'
  puts "Hello World"
RUBY
end

10.5 gsub_file

ファイル内のテキストを置き換えます。

gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'

正規表現を使用して置き換え方法を精密に指定できます。append_fileを使用してコードをファイルの末尾に追加したり、prepend_fileを使用してコードをファイルの冒頭に挿入したりすることもできます。

10.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

10.7 git

gitコマンドを実行します。

git :init
git add: "."
git commit: "-m First commit!"
git add: "onefile.rb", rm: "badfile.cxx"

引数またはオプションとなるハッシュの値は、指定のgitコマンドに渡されます。上の最後の行で示しているように、一行に複数のgitコマンドを記述することができますが、この場合コマンドの実行順序は記載順になるとは限らないので注意が必要です。

10.8 vendor

指定のコードを含むファイルをvendorディレクトリに置きます。

vendor "sekrit.rb", '#極秘

このメソッドにはブロックをひとつ渡すこともできます。

vendor "seeds.rb" do
  "puts 'in your app, seeding your database'"
end

10.9 lib

指定のコードを含むファイルをlibディレクトリに置きます。

lib "special.rb", "p Rails.root"

このメソッドにはブロックをひとつ渡すこともできます。

lib "super_special.rb" do
  "puts 'Super special!'"
end

10.10 rakefile

Railsアプリケーションのlib/tasksディレクトリにRakeファイルをひとつ作成します。

rakefile "test.rake", 'task(:hello) { puts "Hello, there" }'

このメソッドにはブロックをひとつ渡すこともできます。

rakefile "test.rake" do
  %Q{
    task rock: :environment do
      puts "Rockin'"
    end
  }
end

10.11 initializer

Railsアプリケーションのlib/initializersディレクトリにイニシャライザファイルをひとつ作成します。

initializer "begin.rb", "puts 'ここが最初の部分'"

このメソッドにはブロックをひとつ渡すこともでき、文字列が返されます。

initializer "begin.rb" do
  "puts 'this is the beginning'"
end

10.12 generate

指定のジェネレータを実行します。第1引数は実行するジェネレータ名で、残りの引数はジェネレータにそのまま渡されます。

generate "scaffold", "forums title:string description:text"

10.13 rake

Rakeタスクを実行します。

rake "db:migrate"

以下のオプションを利用できます。

  • :env - rakeタスクを実行するときの環境を指定します。
  • :sudo - rakeタスクでsudoを使用するかどうかを指定します。デフォルトはfalseです。

10.14 route

config/routes.rbファイルにテキストを追加します。

route "resources :people"

10.15 readme

テンプレートのsource_pathにあるファイルの内容を出力します。通常このファイルはREADMEです。

readme "README"