edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Railsジェネレータとテンプレートの作成とカスタマイズ

Railsジェネレータは、ワークフローを改善するための重要なツールです。このガイドでは、ジェネレータの作成と既存のジェネレータのカスタマイズ方法を学びます。

このガイドを読み終えると、以下のことがわかります。

1 最初の接触

railsコマンドを使用してアプリケーションを作成すると、実際にはRailsジェネレータを使用しています。その後、bin/rails generateを呼び出すことで、利用可能なすべてのジェネレータのリストを取得できます。

$ rails new myapp
$ cd myapp
$ bin/rails generate

注意:Railsアプリケーションを作成するには、gem install railsを使用してインストールされたRailsのバージョンを使用するrailsグローバルコマンドを使用します。アプリケーションのディレクトリ内では、アプリケーションにバンドルされたRailsのバージョンを使用するbin/railsコマンドを使用します。

Railsに付属しているすべてのジェネレータのリストが表示されます。特定のジェネレータの詳細な説明を表示するには、--helpオプションを指定してジェネレータを呼び出します。例:

$ bin/rails generate scaffold --help

2 最初のジェネレータの作成

ジェネレータは、Thorの上に構築されており、パーシングのための強力なオプションとファイルの操作のための優れたAPIを提供しています。

config/initializers内にinitializer.rbという名前の初期化ファイルを作成するジェネレータを作成しましょう。最初のステップは、lib/generators/initializer_generator.rbというファイルを作成し、以下の内容を記述することです。

class InitializerGenerator < Rails::Generators::Base
  def create_initializer_file
    create_file "config/initializers/initializer.rb", <<~RUBY
      # 初期化の内容をここに追加
    RUBY
  end
end

新しいジェネレータは非常にシンプルです。Rails::Generators::Baseを継承し、1つのメソッド定義を持っています。ジェネレータが呼び出されると、ジェネレータ内の各パブリックメソッドが定義された順序で順次実行されます。私たちのメソッドはcreate_fileを呼び出し、指定されたディレクトリに指定された内容でファイルを作成します。

新しいジェネレータを呼び出すには、次のコマンドを実行します。

$ bin/rails generate initializer

次に、新しいジェネレータの説明を確認しましょう。

$ bin/rails generate initializer --help

Railsは、ActiveRecord::Generators::ModelGeneratorのようなジェネレータが名前空間である場合には、通常、良い説明を導出できますが、この場合はできません。この問題を解決する方法は2つあります。1つ目の方法は、ジェネレータ内でdescを呼び出すことで説明を追加する方法です。

class InitializerGenerator < Rails::Generators::Base
  desc "This generator creates an initializer file at config/initializers"
  def create_initializer_file
    create_file "config/initializers/initializer.rb", <<~RUBY
      # 初期化の内容をここに追加
    RUBY
  end
end

これで、新しいジェネレータの説明を--helpで確認できるようになりました。

2つ目の方法は、ジェネレータと同じディレクトリにUSAGEという名前のファイルを作成することで説明を追加する方法です。次のステップでそれを行います。

3 ジェネレータを使用してジェネレータを作成する

ジェネレータ自体もジェネレータを持っています。InitializerGeneratorを削除し、bin/rails generate generatorを使用して新しいジェネレータを生成しましょう。

$ rm lib/generators/initializer_generator.rb

$ 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::NamedBaseを継承していることに注意してください。これは、ジェネレータが少なくとも1つの引数を期待しており、それが初期化ファイルの名前であり、nameを介してコードで利用できることを意味します。

新しいジェネレータの説明を確認することで、それを確認できます。

$ bin/rails generate initializer --help
Usage:
  bin/rails generate initializer NAME [options]

また、ジェネレータにはsource_rootというクラスメソッドがあります。このメソッドは、テンプレートの場所を指定します(あれば)。デフォルトでは、lib/generators/initializer/templatesディレクトリを指すようになっています。

ジェネレータのテンプレートの動作を理解するために、lib/generators/initializer/templates/initializer.rbというファイルを作成し、次の内容を記述しましょう。

# 初期化の内容をここに追加

そして、ジェネレータが呼び出されたときにこのテンプレートをコピーするようにジェネレータを変更しましょう。 ```ruby 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
      create  config/initializers/core_extensions.rb

$ cat config/initializers/core_extensions.rb
# 初期化の内容をここに追加してください

copy_fileがテンプレートの内容でconfig/initializers/core_extensions.rbを作成したことがわかります(宛先パスで使用されるfile_nameメソッドはRails::Generators::NamedBaseから継承されます)。

4 ジェネレータのコマンドラインオプション

class_optionを使用して、ジェネレータはコマンドラインオプションをサポートできます。例えば:

class InitializerGenerator < Rails::Generators::NamedBase
  class_option :scope, type: :string, default: "app"
end

これでジェネレータは--scopeオプションを使用して呼び出すことができます:

$ bin/rails generate initializer theme --scope dashboard

オプションの値は、ジェネレータのメソッド内でoptionsを介してアクセスできます:

def copy_initializer_file
  @scope = options["scope"]
end

5 ジェネレータの解決

ジェネレータの名前を解決する際、Railsは複数のファイル名を使用してジェネレータを探します。例えば、bin/rails generate initializer core_extensionsを実行すると、Railsは以下のファイルを順番に読み込んでいき、最初に見つかったものを使用します:

  • rails/generators/initializer/initializer_generator.rb
  • generators/initializer/initializer_generator.rb
  • rails/generators/initializer_generator.rb
  • generators/initializer_generator.rb

これらのいずれも見つからない場合はエラーが発生します。

ジェネレータをアプリケーションのlib/ディレクトリに配置したのは、そのディレクトリが$LOAD_PATHに含まれているため、Railsがファイルを見つけて読み込むことができるからです。

6 Railsジェネレータのテンプレートのオーバーライド

Railsは、ジェネレータのテンプレートファイルを解決する際に複数の場所を探します。そのうちの1つがアプリケーションのlib/templates/ディレクトリです。この動作により、Railsの組み込みジェネレータが使用するテンプレートをオーバーライドすることができます。例えば、scaffold controller templatescaffold view templatesをオーバーライドすることができます。

これを実演するために、次の内容のlib/templates/erb/scaffold/index.html.erb.ttファイルを作成しましょう:

<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>

テンプレートは、別のERBテンプレートをレンダリングするERBテンプレートです。したがって、結果としてのテンプレートに表示される<%は、ジェネレータのテンプレートでは<%%とエスケープする必要があります。

それでは、Railsの組み込みのscaffoldジェネレータを実行してみましょう:

$ bin/rails generate scaffold Post title:string
      ...
      create      app/views/posts/index.html.erb
      ...

app/views/posts/index.html.erbの内容は次のとおりです:

<% @posts.count %> Posts

7 Railsジェネレータのオーバーライド

Railsの組み込みジェネレータは、config.generatorsを使用して設定することができます。これには、一部のジェネレータを完全にオーバーライドすることも含まれます。

まず、scaffoldジェネレータの動作を詳しく見てみましょう。

$ bin/rails generate scaffold User name:string
      invoke  active_record
      create    db/migrate/20230518000000_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
      create      app/views/users/_user.html.erb
      invoke    resource_route
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      create      test/system/users_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder

出力からわかるように、scaffoldジェネレータは他のジェネレータを呼び出しています。例えば、scaffold_controllerジェネレータなどです。そして、それらのジェネレータの中にも他のジェネレータを呼び出すものがあります。特に、scaffold_controllerジェネレータは、helperジェネレータを含むいくつかの他のジェネレータを呼び出します。

組み込みのhelperジェネレータを新しいジェネレータでオーバーライドしてみましょう。ジェネレータの名前をmy_helperとします:

$ 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

そして、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", <<~RUBY
      module #{class_name}Helper
        # I'm helping!
      end
    RUBY
  end
end

最後に、組み込みのhelperジェネレータの代わりにmy_helperジェネレータを使用するようにRailsに指示する必要があります。そのためには、config.generatorsを使用します。config/application.rbに以下を追加しましょう:

config.generators do |g|
  g.helper :my_helper
end

これで、scaffoldジェネレータを再度実行すると、my_helperジェネレータが動作することがわかります:

$ bin/rails generate scaffold Article body:text
      ...
      invoke  scaffold_controller
      ...
      invoke    my_helper
      create      app/helpers/articles_helper.rb
      ...

注意:組み込みのhelperジェネレータの出力には「invoke test_unit」という記述が含まれていることに気付くかもしれませんが、my_helperの出力には含まれていません。helperジェネレータはデフォルトではテストを生成しませんが、hook_forを使用してテストを生成するフックを提供しています。同様に、MyHelperGeneratorクラスにhook_for :test_framework, as: :helperを含めることで、同じことを行うことができます。詳細については、hook_forのドキュメントを参照してください。

7.1 ジェネレータのフォールバック

特定のジェネレータをオーバーライドする別の方法として、フォールバックを使用する方法があります。フォールバックを使用すると、ジェネレータの名前空間が別のジェネレータの名前空間に委譲することができます。 例えば、test_unit:model ジェネレータを my_test_unit:model ジェネレータで上書きしたい場合を考えましょう。ただし、test_unit:controller のような他の test_unit:* ジェネレータはすべて置き換えたくありません。

まず、lib/generators/my_test_unit/model/model_generator.rbmy_test_unit:model ジェネレータを作成します。

module MyTestUnit
  class ModelGenerator < Rails::Generators::NamedBase
    source_root File.expand_path("templates", __dir__)

    def do_different_stuff
      say "Doing different stuff..."
    end
  end
end

次に、config.generators を使用して test_framework ジェネレータを my_test_unit として設定しますが、my_test_unit:* ジェネレータが見つからない場合は test_unit:* にフォールバックするように設定します。

config.generators do |g|
  g.test_framework :my_test_unit, fixture: false
  g.fallbacks[:my_test_unit] = :test_unit
end

これで、スキャフォールドジェネレータを実行すると、my_test_unittest_unit の代わりに使用されますが、モデルテストのみが影響を受けます。

$ bin/rails generate scaffold Comment body:text
      invoke  active_record
      create    db/migrate/20230518000000_create_comments.rb
      create    app/models/comment.rb
      invoke    my_test_unit
    Doing different stuff...
      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
      create      app/views/comments/_comment.html.erb
      invoke    resource_route
      invoke    my_test_unit
      create      test/controllers/comments_controller_test.rb
      create      test/system/comments_test.rb
      invoke    helper
      create      app/helpers/comments_helper.rb
      invoke      my_test_unit
      invoke    jbuilder
      create      app/views/comments/index.json.jbuilder
      create      app/views/comments/show.json.jbuilder

8 アプリケーションテンプレート

アプリケーションテンプレートは特別な種類のジェネレータです。これらはすべてのジェネレータヘルパーメソッドを使用できますが、RubyクラスではなくRubyスクリプトとして書かれます。以下に例を示します。

# template.rb

if yes?("Would you like to install Devise?")
  gem "devise"
  devise_model = ask("What would you like the user model to be called?", default: "User")
end

after_bundle do
  if devise_model
    generate "devise:install"
    generate "devise", devise_model
    rails_command "db:migrate"
  end

  git add: ".", commit: %(-m 'Initial commit')
end

まず、テンプレートはユーザーに Devise をインストールするかどうか尋ねます。ユーザーが「はい」(または「y」)と回答すると、テンプレートは Gemfile に Devise を追加し、Deviseユーザーモデルの名前をユーザーに尋ねます(デフォルトは User)。その後、bundle install が実行された後、テンプレートはDeviseジェネレータと rails db:migrate を実行します。最後に、テンプレートはアプリケーションディレクトリ全体を git add および git commit します。

新しいRailsアプリケーションを生成する際に、rails new コマンドに -m オプションを渡すことで、テンプレートを実行できます。

$ rails new my_cool_app -m path/to/template.rb

または、既存のアプリケーション内で bin/rails app:template を使用してテンプレートを実行できます。

$ bin/rails app:template LOCATION=path/to/template.rb

テンプレートはローカルに保存する必要はありません。パスの代わりにURLを指定することもできます。

$ rails new my_cool_app -m http://example.com/template.rb
$ bin/rails app:template LOCATION=http://example.com/template.rb

9 ジェネレータヘルパーメソッド

Thorは、Thor::Actionsを介して多くのジェネレータヘルパーメソッドを提供しています。以下にいくつかの例を示します。

さらに、RailsはRails::Generators::Actionsを介して多くのヘルパーメソッドを提供しています。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。