homeASCIIcasts

216: Generators in Rails 3 

(view original Railscast)

Other translations: It Es

If you’ve used Rails at all then you’ll be familiar with Rails generators and will have used the script/generate command to create models, controllers, scaffolds and so on. The generators in Rails 3 have been rewritten and are completely different from those in Rails 2. For one thing they are now much more modular, which means that you can customize them to suit your preferences.

If we run rails g from the root directory of a Rails 3 application we’ll see a list of the generators available to that application. If we haven’t created any generators of our own then we’ll only see the generators that are included with Rails 3: controller, generator, helper, integration_test, mailer, metal, migration, model, observer, performance_test, plugin, resource, scaffold, scaffold_controller, session_migration and stylesheets. For the most part these generators behave the same way as their Rails 2 counterparts.

If we run a generator with the --help option it will show help information for that generator along with a list of options. If we try this with the scaffold generator we’ll see that there are a lot of options available and these options give us a lot of flexibility in customizing how the generator behaves.

$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]
Options:
  -c, --scaffold-controller=NAME  # Scaffold controller to be invoked
                                  # Default: scaffold_controller
      [--singleton]               # Supply to create a singleton controller
      [--force-plural]            # Forces the use of a plural ModelName
  -y, [--stylesheets]             # Indicates when to generate stylesheets
                                  # Default: true
  -o, --orm=NAME                  # Orm to be invoked
                                  # Default: active_record
ScaffoldController options:
  -e, [--template-engine=NAME]  # Template engine to be invoked
                                # Default: erb
      [--helper]                # Indicates when to generate helper
                                # Default: true
  -t, [--test-framework=NAME]   # Test framework to be invoked
                                # Default: test_unit
Runtime options:
  -f, [--force]    # Overwrite files that already exist
  -p, [--pretend]  # Run but do not make any changes
  -q, [--quiet]    # Supress status output
  -s, [--skip]     # Skip files that already exist
TestUnit options:
  -r, [--fixture-replacement=NAME]  # Fixture replacement to be invoked
      [--fixture]                   # Indicates when to generate fixture
                                    # Default: true
ActiveRecord options:
  [--parent=PARENT]  # The parent class for the generated model
  [--timestamps]     # Indicates when to generate timestamps
                     # Default: true
  [--migration]      # Indicates when to generate migration
                     # Default: true

Note that some of the options have default values. For example the --stylesheets option defaults to true. But what if we don’t want the stylesheets created when we generate a scaffold? Well, with a boolean option that defaults to true we can prefix the option with --no to disable that option. To demonstrate this we’ll create a new scaffold called project without a stylesheet.

$ rails g scaffold project name:string --no-stylesheets
      invoke  active_record
      create    db/migrate/20100602201538_create_projects.rb
      create    app/models/project.rb
      invoke    test_unit
      create      test/unit/project_test.rb
      create      test/fixtures/projects.yml
       route  resources :projects
      invoke  scaffold_controller
      create    app/controllers/projects_controller.rb
      invoke    erb
      create      app/views/projects
      create      app/views/projects/index.html.erb
      create      app/views/projects/edit.html.erb
      create      app/views/projects/show.html.erb
      create      app/views/projects/new.html.erb
      create      app/views/projects/_form.html.erb
      invoke    test_unit
      create      test/functional/projects_controller_test.rb
      invoke    helper
      create      app/helpers/projects_helper.rb
      invoke      test_unit
      create        test/unit/helpers/projects_helper_test.rb

If we look at the output from the command we’ll see that no stylesheet files were generated because we have disabled that option.

As well as the list of created files the output from running the generator shows a number of calls to invoke and these lines indicate where other generators are invoked. This means that the scaffold generator doesn’t do much work by itself, instead delegating to other generators such as the active_record generator which creates the model file and a migration. The active_record generator then calls another generator, test_unit, to create a unit test file and a fixture. This pattern is repeated further down with the scaffold_controller generator invoking the erb, test_unit and helper generators to create all of the files connected to the controller and its associated views. This makes the system very modular and means that we can replace pieces of it with other generators. If we want to use Haml instead of erb in the views, or Shoulda or RSpec instead of Test::Unit for testing we can plug these generators in instead.

Now that we know this we can look again at the list of options that the help for the scaffold generator again and they should begin to make more sense. For example the --orm option allows us to change the ORM that generates the models for us so that we could use DataMapper instead of the default ActiveRecord. Further down the options list is a list of ActiveRecord options that apply to the ActiveRecord version of the generator along with the options for the TestUnit and ScaffoldController generators.

Changing The Default Options

While it’s useful to be able to pass in options such as --no-stylesheets to generators it would be far more useful if we could change the default options and in Rails 3 we can do this on a per-application basis by modifying the application.rb file. The default application.rb file generated when we create an application has the following commented-out section in it.

/config/application.rb

# Configure generators values. Many other options are available, be sure to check the documentation.
# config.generators do |g|
#   g.orm             :active_record
#   g.template_engine :erb
#   g.test_framework  :test_unit, :fixture => true
# end

If we uncomment this section we can override the default options for all of the generators in the application. The options that are provided as an example match the default options so we can replace them with our own. To make the stylesheets option false by default we can write:

/config/application.rb

# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
end

If we run the help command for the scaffold generator again it will reflect the new defaults and the --stylesheets option will no longer be shown as having a default of true.

$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]
Options:
  -y, [--stylesheets]             # Indicates when to generate stylesheets
  -c, --scaffold-controller=NAME  # Scaffold controller to be invoked
                                  # Default: scaffold_controller
      [--singleton]               # Supply to create a singleton controller
      [--force-plural]            # Forces the use of a plural ModelName
  -o, --orm=NAME                  # Orm to be invoked
                                  # Default: active_record

Next we’ll try something a little more adventurous and change the test framework to be Shoulda instead of Test::Unit and replace the fixtures with Factory Girl by changing the default fixture-replacement option.

To do this we first need to specify the relevant gems in the application’s Gemfile. We’ll only need these gems in the test environment so we’ll place them in a group.

/Gemfile

group :test do
  gem "shoulda"
  gem "factory_girl"
end

Unfortunately these two gems don’t include generators for Rails 3 but we can use another gem called rails3-generators that includes generators for a number of Rails 3 plugins and gems, including Factory Girl and Shoulda. We’ll add this gem to the Gemfile too, but only add it to the development group so that it’s only used in the development environment.

/Gemfile

gem "rails3-generators", :group => :development

That done we’ll need to run

bundle install

to install the gems. Once the gems have installed we can run rails g to see a list of the available generators. At the end of the list there should now be all of the generators that are created by the rails3-generators gem.

Now that we have these new generators we can modify the generator configuration in application.rb and add the defaults we want for the test_framework and fixture_replacement options.

/config/application.rb

# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
  g.test_framework :shoulda
  g.fixture_replacement :factory_girl
end

When we run the scaffold generator help command again now it will have changed to show the updated test framework and fixture replacement.

$ rails g scaffold --help
Usage:
  rails generate scaffold NAME [field:type field:type] [options]
  ...
ActiveRecord options:
  [--parent=PARENT]      # The parent class for the generated model
  [--migration]          # Indicates when to generate migration
                         # Default: true
  [--timestamps]         # Indicates when to generate timestamps
                         # Default: true
  [--textframework=NAME] # Test framework to be invoked
                         # Default: shoulda
Shoulda options:
  [--fixture-replacement=NAME]  # Fixture replacement to be invoked
                                # Default: factory_girl
  [--dir=DIR]                   # The directory where the model tests should go
                                # Default: test/unit

We can put this to the test by generating a new scaffold for a task model.

$ rails g scaffold task project_id:integer name:string
      invoke  active_record
      create    db/migrate/20100604202823_create_tasks.rb
      create    app/models/task.rb
      invoke    shoulda
      create      test/unit/task_test.rb
      invoke      factory_girl
      create        test/factories/tasks.rb
       route  resources :tasks
      invoke  scaffold_controller
      create    app/controllers/tasks_controller.rb
      invoke    erb
      create      app/views/tasks
      create      app/views/tasks/index.html.erb
      create      app/views/tasks/edit.html.erb
      create      app/views/tasks/show.html.erb
      create      app/views/tasks/new.html.erb
      create      app/views/tasks/_form.html.erb
       error    shoulda [not found]
      invoke    helper
      create      app/helpers/tasks_helper.rb
       error      shoulda [not found]

At the top of the output we’ll see that the shoulda and factory_girl generators were invoked correctly but that further down the scaffold_controller generator couldn’t find a shoulda generator for erb or for the helper file. If we look at the list of helpers again we can see that we only have shoulda generators for models and controllers.

$ rails g
  ...
Shoulda:
  shoulda:controller
  shoulda:model

What should we do here? Well, if we look at the Rails Guides page for generators we’ll see that it’s possible to add fallback generators so that if a certain generator isn’t found a different one will be used. All we need to do is modify the config block in application.rb again.

/config/application.rb

# Configure generators values. Many other options are available, be sure to check the documentation.
config.generators do |g|
  g.stylesheets false
  g.test_framework :shoulda
  g.fallbacks[:shoulda] = :test_unit 
  g.fixture_replacement :factory_girl
end

Now we can successfully generate scaffolds with Shoulda and the generators will fall back to Test::Unit if a Shoulda generator can’t be found.

Customizing Templates

The final thing we’ll cover in this episode is how to customize the generated templates. Below is the default view code for the index action for the task controller we just generated.

/app/views/tasks/index.html.erb

<h1>Listing tasks</h1>
<table>
  <tr>
    <th></th>
    <th></th>
    <th></th>
  </tr>
<% @tasks.each do |task| %>
  <tr>
    <td><%= link_to 'Show', task %></td>
    <td><%= link_to 'Edit', edit_task_path(task) %></td>
    <td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>

Let’s say that we want to remove the line break tag at the end of the template and wrap the link at the bottom in a paragraph element instead. How can we change the template so that every generated index view is customized that way?

To do this we can create new custom templates within a new templates directory under the application’s lib directory. The generators will look here for template files before using the defaults. In order to determine which template to override we’ll have to take a look at the Rails source code. The code for the generators is held in the railties/lib/rails/generators directory and the default view templates are in a erb/scaffold/templates/ directory under that. We can copy the contents of the index.html.erb file in this directory and customize it to suit our needs.

We need to create a similar directory structure underneath the generators directory and so our customized index template needs to be at /lib/templates/erb/scaffold/index.html.erb. We can then paste in the default template file and change it to suit. Once we’ve done that if we create a new scaffold, say for a model called Category, then we’ll see the index view based on our custom template.

rails g scaffold category name:string

/app/views/categories/index.html.erb

<h1>Listing categories</h1>
<table>
  <tr>
    <th>Name</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>
<% @categories.each do |category| %>
  <tr>
    <td><%= category.name %></td>
    <td><%= link_to 'Show', category %></td>
    <td><%= link_to 'Edit', edit_category_path(category) %></td>
    <td><%= link_to 'Destroy', category, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>
<p><%= link_to 'New Category', new_category_path %></p>

That’s it for this episode. The generators in Rails 3 are a big improvement over the ones in Rails 2 and much easier to customize.