216: Generators in Rails 3
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:
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,
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.
# 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:
# 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
$ 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
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.
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.
gem "rails3-generators", :group => :development
That done we’ll need to run
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
# 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
$ 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
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
# 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.
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.
<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
<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.