Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
More atrubyonrails.org:

Creating and Customizing Rails Generators & Templates

Rails generators are an essential tool for improving your workflow. With thisguide you will learn how to create generators and customize existing ones.

After reading this guide, you will know:

  • How to see which generators are available in your application.
  • How to create a generator using templates.
  • How Rails searches for generators before invoking them.
  • How to customize your scaffold by overriding generator templates.
  • How to customize your scaffold by overriding generators.
  • How to use fallbacks to avoid overwriting a huge set of generators.
  • How to create an application template.

1. First Contact

When you create an application using therails command, you are in fact usinga Rails generator. After that, you can get a list of all available generators byinvokingbin/rails generate:

$railsnew myapp$cdmyapp$bin/railsgenerate

To create a Rails application we use therails global command which usesthe version of Rails installed viagem install rails. When inside thedirectory of your application, we use thebin/rails command which uses theversion of Rails bundled with the application.

You will get a list of all generators that come with Rails. To see a detaileddescription of a particular generator, invoke the generator with the--helpoption. For example:

$bin/railsgenerate scaffold--help

2. Creating Your First Generator

Generators are built on top ofThor, whichprovides powerful options for parsing and a great API for manipulating files.

Let's build a generator that creates an initializer file namedinitializer.rbinsideconfig/initializers. The first step is to create a file atlib/generators/initializer_generator.rb with the following content:

classInitializerGenerator<Rails::Generators::Basedefcreate_initializer_filecreate_file"config/initializers/initializer.rb",<<~RUBY      # Add initialization content here    RUBYendend

Our new generator is quite simple: it inherits fromRails::Generators::Baseand has one method definition. When a generator is invoked, each public methodin the generator is executed sequentially in the order that it is defined. Ourmethod invokescreate_file, which will create a file at the givendestination with the given content.

To invoke our new generator, we run:

$bin/railsgenerate initializer

Before we go on, let's see the description of our new generator:

$bin/railsgenerate initializer--help

Rails is usually able to derive a good description if a generator is namespaced,such asActiveRecord::Generators::ModelGenerator, but not in this case. We cansolve this problem in two ways. The first way to add a description is by callingdesc inside our generator:

classInitializerGenerator<Rails::Generators::Basedesc"This generator creates an initializer file at config/initializers"defcreate_initializer_filecreate_file"config/initializers/initializer.rb",<<~RUBY      # Add initialization content here    RUBYendend

Now we can see the new description by invoking--help on the new generator.

The second way to add a description is by creating a file namedUSAGE in thesame directory as our generator. We are going to do that in the next step.

3. Creating Generators with Generators

Generators themselves have a generator. Let's remove ourInitializerGeneratorand usebin/rails generate generator to generate a new one:

$rmlib/generators/initializer_generator.rb$bin/railsgenerate 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

This is the generator just created:

classInitializerGenerator<Rails::Generators::NamedBasesource_rootFile.expand_path("templates",__dir__)end

First, notice that the generator inherits fromRails::Generators::NamedBaseinstead ofRails::Generators::Base. This means that our generator expects atleast one argument, which will be the name of the initializer and will beavailable to our code vianame.

We can see that by checking the description of the new generator:

$bin/railsgenerate initializer--helpUsage:  bin/rails generate initializer NAME [options]

Also, notice that the generator has a class method calledsource_root.This method points to the location of our templates, if any. By default itpoints to thelib/generators/initializer/templates directory that was justcreated.

In order to understand how generator templates work, let's create the filelib/generators/initializer/templates/initializer.rb with the followingcontent:

# Add initialization content here

And let's change the generator to copy this template when invoked:

classInitializerGenerator<Rails::Generators::NamedBasesource_rootFile.expand_path("templates",__dir__)defcopy_initializer_filecopy_file"initializer.rb","config/initializers/#{file_name}.rb"endend

Now let's run our generator:

$bin/railsgenerate initializer core_extensions      create  config/initializers/core_extensions.rb$catconfig/initializers/core_extensions.rb#Add initialization content here

We see thatcopy_file createdconfig/initializers/core_extensions.rbwith the contents of our template. (Thefile_name method used in thedestination path is inherited fromRails::Generators::NamedBase.)

4. Generator Command Line Options

Generators can support command line options usingclass_option. Forexample:

classInitializerGenerator<Rails::Generators::NamedBaseclass_option:scope,type: :string,default:"app"end

Now our generator can be invoked with a--scope option:

$bin/railsgenerate initializer theme--scope dashboard

Option values are accessible in generator methods viaoptions:

defcopy_initializer_file@scope=options["scope"]end

5. Generator Resolution

When resolving a generator's name, Rails looks for the generator using multiplefile names. For example, when you runbin/rails generate initializer core_extensions,Rails tries to load each of the following files, in order, until one is found:

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

If none of these are found, an error will be raised.

We put our generator in the application'slib/ directory because thatdirectory is in$LOAD_PATH, thus allowing Rails to find and load the file.

6. Overriding Rails Generator Templates

Rails will also look in multiple places when resolving generator template files.One of those places is the application'slib/templates/ directory. Thisbehavior allows us to override the templates used by Rails' built-in generators.For example, we could override thescaffold controller template or thescaffold view templates.

To see this in action, let's create alib/templates/erb/scaffold/index.html.erb.ttfile with the following contents:

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

Note that the template is an ERB template that rendersanother ERB template.So any<% that should appear in theresulting template must be escaped as<%% in thegenerator template.

Now let's run Rails' built-in scaffold generator:

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

The contents ofapp/views/posts/index.html.erb is:

<%@posts.count%> Posts

7. Overriding Rails Generators

Rails' built-in generators can be configured viaconfig.generators,including overriding some generators entirely.

First, let's take a closer look at how the scaffold generator works.

$bin/railsgenerate 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

From the output, we can see that the scaffold generator invokes othergenerators, such as thescaffold_controller generator. And some of thosegenerators invoke other generators too. In particular, thescaffold_controllergenerator invokes several other generators, including thehelper generator.

Let's override the built-inhelper generator with a new generator. We'll namethe generatormy_helper:

$bin/railsgenerate generatorrails/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

And inlib/generators/rails/my_helper/my_helper_generator.rb we'll definethe generator as:

classRails::MyHelperGenerator<Rails::Generators::NamedBasedefcreate_helper_filecreate_file"app/helpers/#{file_name}_helper.rb",<<~RUBY      module#{class_name}Helper        # I'm helping!      end    RUBYendend

Finally, we need to tell Rails to use themy_helper generator instead of thebuilt-inhelper generator. For that we useconfig.generators. Inconfig/application.rb, let's add:

config.generatorsdo|g|g.helper:my_helperend

Now if we run the scaffold generator again, we see themy_helper generator inaction:

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

You may notice that the output for the built-inhelper generatorincludes "invoke test_unit", whereas the output formy_helper does not.Although thehelper generator does not generate tests by default, it doesprovide a hook to do so usinghook_for. We can do the same by includinghook_for :test_framework, as: :helper in theMyHelperGenerator class. Seethehook_for documentation for more information.

7.1. Generators Fallbacks

Another way to override specific generators is by usingfallbacks. A fallbackallows a generator namespace to delegate to another generator namespace.

For example, let's say we want to override thetest_unit:model generator withour ownmy_test_unit:model generator, but we don't want to replace all of theothertest_unit:* generators such astest_unit:controller.

First, we create themy_test_unit:model generator inlib/generators/my_test_unit/model/model_generator.rb:

moduleMyTestUnitclassModelGenerator<Rails::Generators::NamedBasesource_rootFile.expand_path("templates",__dir__)defdo_different_stuffsay"Doing different stuff..."endendend

Next, we useconfig.generators to configure thetest_framework generator asmy_test_unit, but we also configure a fallback such that any missingmy_test_unit:* generators resolve totest_unit:*:

config.generatorsdo|g|g.test_framework:my_test_unit,fixture:falseg.fallbacks[:my_test_unit]=:test_unitend

Now when we run the scaffold generator, we see thatmy_test_unit has replacedtest_unit, but only the model tests have been affected:

$bin/railsgenerate 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. Application Templates

Application templates are a special kind of generator. They can use all of thegenerator helper methods, but are written as a Rubyscript instead of a Ruby class. Here is an example:

# template.rbifyes?("Would you like to install Devise?")gem"devise"devise_model=ask("What would you like the user model to be called?",default:"User")endafter_bundledoifdevise_modelgenerate"devise:install"generate"devise",devise_modelrails_command"db:migrate"endgitadd:".",commit:%(-m 'Initial commit')end

First, the template asks the user whether they would like to install Devise.If the user replies "yes" (or "y"), the template adds Devise to theGemfile,and asks the user for the name of the Devise user model (defaulting toUser).Later, afterbundle install has been run, the template will run the Devisegenerators andrails db:migrate if a Devise model was specified. Finally, thetemplate willgit add andgit commit the entire app directory.

We can run our template when generating a new Rails application by passing the-m option to therails new command:

$railsnew my_cool_app-m path/to/template.rb

Alternatively, we can run our template inside an existing application withbin/rails app:template:

$bin/railsapp:templateLOCATION=path/to/template.rb

Templates also don't need to be stored locally — you can specify a URL insteadof a path:

$railsnew my_cool_app-m http://example.com/template.rb$bin/railsapp:templateLOCATION=http://example.com/template.rb

9. Generator Helper Methods

Thor provides many generator helper methods viaThor::Actions, such as:

In addition to those, Rails also provides many helper methods viaRails::Generators::Actions, such as:

10. Testing Generators

Rails provides testing helper methods viaRails::Generators::Testing::Behaviour, such as:

If running tests against generators you will need to setRAILS_LOG_TO_STDOUT=true in order for debugging tools to work.

RAILS_LOG_TO_STDOUT=true ./bin/testtest/generators/actions_test.rb

In addition to those, Rails also provides additional assertions viaRails::Generators::Testing::Assertions.



Back to top
[8]ページ先頭

©2009-2025 Movatter.jp