Railsの各種ジェネレータとアプリケーションテンプレートは、定型コードを自動的に生成してワークフローを改善するツールとして非常に有用です。
このガイドの内容:
railsコマンドでRailsアプリケーションを作成すると、実はRailsのジェネレータを利用したことになります。以後は、bin/rails generateを実行すれば、その時点でアプリケーションから利用可能なすべてのジェネレータのリストが表示されます。
$ rails new myapp $ cd myapp $ bin/rails generate
Railsアプリケーションを新しく作成するときは、gem install railsでインストールしたrails gemのグローバルなrailsコマンドを使いますが、作成したアプリケーションのディレクトリ内では、そのアプリケーション内にバンドルされているbin/railsコマンドを使う点が異なります。
Railsで利用可能なすべてのジェネレータのリストを表示できます。特定のジェネレータのヘルプを表示するには、そのジェネレータ名に続けて以下のように--helpオプションを指定します。
$ bin/rails generate scaffold --help
ジェネレータはThor gemの上に構築されています。Thorは強力な解析オプションと優れたファイル操作APIを提供しています。
具体例として、config/initializersディレクトリの下にinitializer.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 "このジェネレータはconfig/initializersにイニシャライザファイルを作成します" def create_initializer_file create_file "config/initializers/initializer.rb", <<~RUBY # 初期化時のコンテンツをここに追加する RUBY end end
これで、--helpを付けて新しいジェネレータを呼び出すと新しい説明文が表示されるようになりました。
説明文を追加する2つ目の方法は、ジェネレータと同じディレクトリにUSAGEという名前のファイルを作成することです。次に、この方法で実際に説明文を追加してみましょう。
Railsには、ジェネレータを生成するためのジェネレータもあります。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::Baseではなく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を作成して以下のコンテンツを追加してみましょう。
# 初期化用のコンテンツをここに追加する
続いてジェネレータを変更し、呼び出されたときにこのテンプレートをコピーするようにします。
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から継承されます)。
ジェネレータでは、以下のように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
Railsがジェネレータ名を解決するときは、複数のファイル名を使ってジェネレータを探索します。たとえば、bin/rails generate initializer core_extensionsを実行すると、Railsはジェネレータが見つかるまで以下の順にファイルを探索します。
rails/generators/initializer/initializer_generator.rbgenerators/initializer/initializer_generator.rbrails/generators/initializer_generator.rbgenerators/initializer_generator.rbジェネレータがどのファイルにも見つからない場合は、エラーメッセージが表示されます。
上の例でアプリケーションのlib/ディレクトリの下にファイルを置いているのは、このディレクトリが$LOAD_PATHに含まれているからです。これにより、Railsがこのファイルを検索して読み込めるようになります。
Railsは、ジェネレータのテンプレートファイルを解決するときにも複数の場所を探索します。アプリケーションのlib/templates/ディレクトリも探索場所の1つです。この振る舞いのおかげで、Railsの組み込みジェネレータで使われるテンプレートをオーバーライドできます。たとえば、コントローラのscaffoldテンプレートやビューのscaffoldテンプレートをオーバーライドできます。
これを実際に行うために、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
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 # 私はヘルパー 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のドキュメントを参照してください。
特定のジェネレータをオーバーライドする別の方法は、フォールバックを使う方法です。フォールバックを使うと、あるジェネレータの名前空間を別のジェネレータの名前空間に委譲できます。
たとえば、my_test_unit:modelジェネレータを作成してtest_unit:modelジェネレータをオーバーライドしたいとします。しかし、test_unit:controllerジェネレータなどの他のtest_unit:*ジェネレータはオーバーライドしたくないとします。
最初に、my_test_unit:modelジェネレータをlib/generators/my_test_unit/model/model_generator.rbファイルに作成します。
module MyTestUnit class ModelGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def do_different_stuff say "別の作業を実行中..." 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
これで、scaffoldジェネレータを実行すると、my_test_unitジェネレータがtest_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
アプリケーションテンプレートは、ジェネレータと若干異なる点があります。ジェネレータは、既存のRailsアプリケーションにモデルやビューなどのファイルを追加しますが、テンプレートは新しいRailsアプリケーションのセットアップを自動化するのに使われます。アプリケーションテンプレートは、新しいRailsアプリケーションを生成した直後にカスタマイズするRubyスクリプトであり、通常はtemplate.rbという名前です。
Railsアプリケーションを作成するときにアプリケーションテンプレートを使う方法を見てみましょう。
最初は、サンプルのRubyスクリプトテンプレートを作成してみましょう。
以下のテンプレートは、ユーザーに確認した後、GemfileにDeviseを追加し、Deviseユーザーモデル名を入力できるようにします。bundle installの実行後、テンプレートはDeviseジェネレータとマイグレーションを実行します。最後に、git addとgit commitを実行します。
# template.rb if yes?("Deviseをインストールしますか?") gem "devise" devise_model = ask("ユーザモデル名は何にしますか?", 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
このテンプレートを使って新しいRailsアプリケーションを作成するには、-mオプションでテンプレートの場所を指定します。
$ rails new blog -m ~/template.rb
これで、新規Railsアプリケーションがblogという名前で作成されるときに、Devise gemも設定されます。
app:templateコマンドを使えば、既存のRailsアプリケーションにテンプレートを適用することも可能です。
この場合、テンプレートファイルの場所はLOCATION環境変数で指定する必要があります。
$ bin/rails app:template LOCATION=~/template.rb
テンプレートは必ずしもローカルに保存する必要はありません。ファイルパスの代わりにURLも指定できます。
$ rails new blog -m https://example.com/template.rb $ bin/rails app:template LOCATION=https://example.com/template.rb
第三者が提供するリモートスクリプトを実行するときは注意が必要です。テンプレートは単なるRubyスクリプトなので、ローカルマシンを危険にさらすコード(ウイルスのダウンロード、ファイルの削除、個人ファイルのサーバーへのアップロードなど)が仕込まれやすくなる可能性があります。
上述のtemplate.rbファイルでは、after_bundleやrails_commandなどのヘルパーメソッドを使い、yes?のようなユーザーインタラクティビティも追加しています。これらのメソッドはすべてRails Template APIの一部です。これらのメソッドの利用例を次のセクションで示します。
ジェネレータと、テンプレートのRubyスクリプトは、DSL(ドメイン固有言語)を使っていくつかのヘルパーメソッドにアクセスできます。これらのメソッドはRailsジェネレータAPIの一部であり、詳しくはThor::ActionsやRails::Generators::ActionsのAPIドキュメントで確認できます。
もう一つの典型的なRailsテンプレートの例を見てみましょう。このテンプレートはモデルをscaffoldで生成してからマイグレーションを実行し、変更をgitでコミットします。
# template.rb generate(:scaffold, "person name:string") route "root to: 'people#index'" rails_command("db:migrate") after_bundle do git :init git add: "." git commit: %Q{ -m 'Initial commit' } end
以下の例で使われているコードスニペットは、すべて上記のtemplate.rbファイルなどのテンプレートファイルで利用可能です。
add_sourceadd_sourceメソッドは、指定したソース(gemの取得元)を、生成されるアプリケーションのGemfileに追加します。
add_source "https://rubygems.org"
このメソッドにブロックを渡すと、ブロック内のgemエントリがソースグループにラップされます。たとえば、gemを"http://gems.github.com"から取得する必要がある場合は以下のようにします。
add_source "http://gems.github.com/" do gem "rspec-rails" end
after_bundleafter_bundleメソッドは、gemのバンドルが完了した後に実行されるコールバックを登録します。
たとえば、tailwindcss-railsとdeviseのインストールコマンドは、それらのgemがバンドルされた後に実行するのが合理的です。
# gemをインストールする after_bundle do # TailwindCSSをインストールする rails_command "tailwindcss:install" # Deviseをインストールする generate "devise:install" end
このコールバックは、rails newコマンドで--skip-bundleオプションを指定した場合でも実行される点にご注意ください。
environmentenvironmentメソッドは、config/application.rbのApplicationクラス内に行を追加します。options[:env]が指定されている場合、その行はconfig/environments/ディレクトリ内の対応するファイルに追加されます。
environment 'config.action_mailer.default_url_options = {host: "http://yourwebsite.example.com"}', env: "production"
上のコードは、config/environments/production.rbに設定行を追加します。
gemgemメソッドは、指定のgemエントリを、生成されたアプリケーションのGemfileに追加します。
たとえば、アプリケーションがdevise gemとtailwindcss-rails gemに依存している場合は、以下のようにします。
gem "devise" gem "tailwindcss-rails"
このメソッドは、gemをGemfileに追加するだけで、gemのインストールは行わない点にご注意ください。
gemのバージョンも指定できます。
gem "devise", "~> 4.9.4"
Gemfileにコメント付きでgemを追加することも可能です。
gem "devise", comment: "Add devise for authentication."
gem_groupgem_groupメソッドは、gemエントリをグループにラップします。たとえば、rspec-railsをdevelopmentグループとtestグループでのみ読み込むには、以下のようにします。
gem_group :development, :test do gem "rspec-rails" end
generategenerateメソッドを使うと、template.rbファイル内でRailsジェネレータを呼び出せます。
たとえば、scaffoldジェネレータを呼び出してPersonモデルを生成するには、以下のようにします。
generate(:scaffold, "person", "name:string", "address:text", "age:number")
gitgitヘルパーメソッドを使うと、Railsテンプレート内で任意のgitコマンドを実行できます。
git :init git add: "." git commit: "-a -m 'Initial commit'"
initializer、vendor、lib、fileinitializerヘルパーメソッドは、生成されたアプリケーションのconfig/initializers/ディレクトリにイニシャライザファイルを追加します。
template.rbファイルに以下のコードを追加すると、アプリケーションでObject#not_nil?とObject#not_blank?を使えるようになります。
initializer "not_methods.rb", <<-CODE class Object def not_nil? !nil? end def not_blank? !blank? end end CODE
同様に、libメソッドはlib/ディレクトリにファイルを作成し、
vendorメソッドはvendor/ディレクトリにファイルを作成します。
fileメソッドはcreate_fileのエイリアスです。Rails.rootからの相対パスを受け取って、必要なディレクトリとファイルをすべて作成します。
file "app/components/foo.rb", <<-CODE class Foo end CODE
上のコードはapp/components/ディレクトリを作成し、その中にfoo.rbを配置します。
rakefilerakefileメソッドは、指定のタスクを含む新しいRakeファイルをlib/tasks/ディレクトリに作成します。
rakefile("bootstrap.rake") do <<-TASK namespace :boot do task :strap do puts "I like boots!" end end TASK end
上のコードは、lib/tasks/bootstrap.rakeファイルを作成し、boot:strap rakeタスクを定義します。
runrunメソッドは、任意のコマンドを実行します。たとえば、README.rdocファイルを削除したい場合は、以下のようにします。
run "rm README.rdoc"
rails_commandrails_commandメソッドを使うと、生成されたアプリケーションでRailsコマンドを実行できます。
たとえば、テンプレートのRubyスクリプト内でデータベースをマイグレーションしたい場合は、以下のようにします。
rails_command "db:migrate"
Railsの環境を指定してコマンドを実行することも可能です。
rails_command "db:migrate", env: "production"
abort_on_failureオプションを指定することで、コマンド実行に失敗した場合はアプリケーションの生成を中止することも可能です。
rails_command "db:migrate", abort_on_failure: true
routerouteメソッドは、config/routes.rbファイルにエントリを追加します。
アプリケーションのデフォルトページをPeopleController#indexにするには、以下を追加します。
route "root to: 'person#index'"
この他にも、copy_file、create_file、insert_into_file、insideなどのローカルファイルシステムを操作するヘルパーメソッドが多数用意されています。詳しくはThorのAPIドキュメントを参照してください。以下にそのようなメソッドの例を示します。
insideinsideメソッドは、指定したディレクトリからコマンドを実行できるようにします.
たとえば、新しいアプリケーションからedge railsのコピーへのシンボリックリンクを作成したい場合は、以下のようにします。
inside("vendor") do run "ln -s ~/my-forks/rails rails" end
この他に、ask、[yes?][], [no?][]など、Rubyテンプレートからユーザーと対話できるメソッドもあります。すべてのユーザー対話メソッドについては、Thorのシェルドキュメントで確認できます。以下にask、yes?、no?の例を示します。
askaskメソッドを使うと、ユーザーからの指示を受け付けてテンプレートで利用できます。
たとえば、新しいライブラリの名前をユーザーに尋ねたい場合は、以下のようにします。
lib_name = ask("What do you want to call the shiny library?") lib_name << ".rb" unless lib_name.index(".rb") lib lib_name, <<-CODE class Shiny end CODE
yes?とno?[yes?][]メソッドや[no?][]メソッドを使って、yes/noで答えられる質問を手軽にユーザーに表示して、その答えに基づいて処理の流れを決められます。
たとえば、ユーザーにマイグレーションを実行するかどうか尋ねたい場合は、以下のようにします。
rails_command("db:migrate") if yes?("Run database migrations?") # no? questions acts the opposite of yes?
Railsは、Rails::Generators::Testing::Behaviourで以下のようなテストヘルパーメソッドを提供しています。
ジェネレータに対してテストを実行する場合、デバッグツールが機能するために以下のようにコマンドでRAILS_LOG_TO_STDOUT=trueを指定する必要があります。
RAILS_LOG_TO_STDOUT=true ./bin/test test/generators/actions_test.rb
Railsではその他にも、Rails::Generators::Testing::Assertionsで追加のアサーションを提供しています。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。