Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
More atrubyonrails.org:

Creating and Customizing Rails Generators & Templates

Rails generators and application templates are useful tools that can help improve your workflow by automatically creating boilerplate code. In this guide you will learn:

  • 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 Rails scaffolding by overriding generators and templates.
  • How to use fallbacks to avoid overwriting a huge set of generators.
  • How to use templates to create/customize Rails applications.
  • How to use the Rails Template API to write your own reusable application templates.

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 little different from generators. While generatorsadd files to an existing Rails application (models, views, etc.), templates areused to automate the setup of a new Rails application. Templates are Rubyscripts (typically namedtemplate.rb) that customize new Rails applicationsright after they are generated.

Let's see how to use a template while creating a new Rails application.

8.1. Creating and Using Templates

Let's start with a sample template Ruby script. The below template adds Deviseto theGemfile after asking the user and also allows the user to name theDevise user model. Afterbundle install has been run, the template runs theDevise generators and also runs migrations. Finally, the template doesgit add andgit commit.

# 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

To apply this template while creating a new Rails application, you need toprovide the location of the template using the-m option:

$railsnew blog-m ~/template.rb

The above will create a new Rails application calledblog that has Devise gem configured.

You can also apply templates to an existing Rails application by usingapp:template command. The location of the template needs to be passed in viatheLOCATION environment variable:

$bin/railsapp:templateLOCATION=~/template.rb

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

$railsnew blog-m https://example.com/template.rb$bin/railsapp:templateLOCATION=https://example.com/template.rb

Caution should be taken when executing remote scripts from third parties. Since the template is a plain Ruby script, it can easily contain code that compromises your local machine (such as download a virus, delete files or upload your private files to a server).

The abovetemplate.rb file uses helper methods such asafter_bundle andrails_command and also adds user interactivity with methods likeyes?. Allof these methods are part of theRails TemplateAPI. Thefollowing sections shows how to use more of these methods with examples.

9. Rails Generators API

Generators and the template Ruby scripts have access to several helper methodsusing aDSL (DomainSpecific Language). These methods are part of the Rails Generators API and youcan find more details atThor::Actions andRails::Generators::Actions API documentation.

Here's another example of a typical Rails template that scaffolds a model, runsmigrations, and commits the changes with git:

# template.rbgenerate(:scaffold,"person name:string")route"root to: 'people#index'"rails_command("db:migrate")after_bundledogit:initgitadd:"."gitcommit:%Q{ -m 'Initial commit' }end

All code snippets in the examples below can be used in a templatefile, such as thetemplate.rb file above.

9.1. add_source

Theadd_source method adds the given source to the generated application'sGemfile.

add_source"https://rubygems.org"

If a block is given, gem entries in the block are wrapped into the source group.For example, if you need to source a gem from"http://gems.github.com":

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

9.2. after_bundle

Theafter_bundle method registers a callback to be executed after the gemsare bundled. For example, it would make sense to run the "install" command fortailwindcss-rails anddevise only after those gems are bundled:

# Install gemsafter_bundledo# Install TailwindCSSrails_command"tailwindcss:install"# Install Devisegenerate"devise:install"end

The callbacks get executed even if--skip-bundle has been passed.

9.3. environment

Theenvironment method adds a line inside theApplication class forconfig/application.rb. Ifoptions[:env] is specified, the line is appendedto the corresponding file inconfig/environments.

environment'config.action_mailer.default_url_options = {host: "http://yourwebsite.example.com"}',env:"production"

The above will add the config line toconfig/environments/production.rb.

9.4. gem

Thegem helper adds an entry for the given gem to the generated application'sGemfile.

For example, if your application depends on the gemsdevise andtailwindcss-rails:

gem"devise"gem"tailwindcss-rails"

Note that this method only adds the gem to theGemfile, it does not installthe gem.

You can also specify an exact version:

gem"devise","~> 4.9.4"

And you can also add comments that will be added to theGemfile:

gem"devise",comment:"Add devise for authentication."

9.5. gem_group

Thegem_group helper wraps gem entries inside a group. For example, to loadrspec-railsonly in thedevelopment andtest groups:

gem_group:development,:testdogem"rspec-rails"end

9.6. generate

You can even call a generator from inside atemplate.rb with thegenerate method. The following runs thescaffold rails generator withthe given arguments:

generate(:scaffold,"person","name:string","address:text","age:number")

9.7. git

Rails templates let you run any git command with thegit helper:

git:initgitadd:"."gitcommit:"-a -m 'Initial commit'"

9.8. initializer, vendor, lib, file

Theinitializer helper method adds an initializer to the generatedapplication'sconfig/initializers directory.

After adding the below to thetemplate.rb file, you can useObject#not_nil?andObject#not_blank? in your application:

initializer"not_methods.rb",<<-CODE  class Object    def not_nil?      !nil?    end    def not_blank?      !blank?    end  endCODE

Similarly, thelib method creates a file in thelib/ directory andvendor method creates a file in thevendor/ directory.

There is also afile method (which is an alias forcreate_file), whichaccepts a relative path fromRails.root and creates all the directories andfiles needed:

file"app/components/foo.rb",<<-CODE  class Foo  endCODE

The above will create theapp/components directory and putfoo.rb in there.

9.9. rakefile

Therakefile method creates a new Rake file underlib/tasks with thegiven tasks:

rakefile("bootstrap.rake")do<<-TASK    namespace :boot do      task :strap do        puts "I like boots!"      end    end  TASKend

The above createslib/tasks/bootstrap.rake with aboot:strap rake task.

9.10. run

Therun method executes an arbitrary command. Let's say you want to removetheREADME.rdoc file:

run"rm README.rdoc"

9.11. rails_command

You can run the Rails commands in the generated application with therails_command helper. Let's say you want to migrate the database at somepoint in the template ruby script:

rails_command"db:migrate"

Commands can be run with a different Rails environment:

rails_command"db:migrate",env:"production"

You can also run commands that should abort application generation if they fail:

rails_command"db:migrate",abort_on_failure:true

9.12. route

Theroute method adds an entry to theconfig/routes.rb file. To makePeopleController#index the default page for the application, we can add:

route"root to: 'person#index'"

There are also many helper methods that can manipulate the local file system,such ascopy_file,create_file,insert_into_file, andinside. You can see theThor APIdocumentation for details.Here is an example of one such method:

9.13. inside

Thisinside method enables you to run a command from a given directory.For example, if you have a copy of edge rails that you wish to symlink from yournew apps, you can do this:

inside("vendor")dorun"ln -s ~/my-forks/rails rails"end

There are also methods that allow you to interact with the user from the Ruby template, such asask,yes, andno. You can learn about all user interactivity methods in theThor Shell documentation. Let's see examples of usingask,yes? andno?:

9.14. ask

Theask methods allows you to get feedback from the user and use it in yourtemplates. Let's say you want your user to name the new shiny library you'readding:

lib_name=ask("What do you want to call the shiny library?")lib_name<<".rb"unlesslib_name.index(".rb")liblib_name,<<-CODE  class Shiny  endCODE

9.15. yes? or no?

These methods let you ask questions from templates and decide the flow based onthe user's answer. Let's say you want to prompt the user to run migrations:

rails_command("db:migrate")ifyes?("Run database migrations?")# no? questions acts the opposite of yes?

10. Testing Generators

Rails provides testing helper methods viaRails::Generators::Testing::Behavior, 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