#216 Generators in Rails 3
- Download:
- source codeProject Files in Zip (108 KB)
- mp4Full Size H.264 Video (29.7 MB)
- m4vSmaller H.264 Video (15 MB)
- webmFull Size VP8 Video (35.8 MB)
- ogvFull Size Theora Video (34.3 MB)
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 <span class="passed">create</span> db/migrate/20100602201538_create_projects.rb <span class="passed">create</span> app/models/project.rb invoke test_unit <span class="passed">create</span> test/unit/project_test.rb <span class="passed">create</span> test/fixtures/projects.yml route resources :projects invoke scaffold_controller <span class="passed">create</span> app/controllers/projects_controller.rb invoke erb <span class="passed">create</span> app/views/projects <span class="passed">create</span> app/views/projects/index.html.erb <span class="passed">create</span> app/views/projects/edit.html.erb <span class="passed">create</span> app/views/projects/show.html.erb <span class="passed">create</span> app/views/projects/new.html.erb <span class="passed">create</span> app/views/projects/_form.html.erb invoke test_unit <span class="passed">create</span> test/functional/projects_controller_test.rb invoke helper <span class="passed">create</span> app/helpers/projects_helper.rb invoke test_unit <span class="passed">create</span> 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.
# 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 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.
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
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.
# 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 <span class="passed">create</span> db/migrate/20100604202823_create_tasks.rb <span class="passed">create</span> app/models/task.rb invoke shoulda <span class="passed">create</span> test/unit/task_test.rb invoke factory_girl <span class="passed">create</span> test/factories/tasks.rb route resources :tasks invoke scaffold_controller <span class="passed">create</span> app/controllers/tasks_controller.rb invoke erb <span class="passed">create</span> app/views/tasks <span class="passed">create</span> app/views/tasks/index.html.erb <span class="passed">create</span> app/views/tasks/edit.html.erb <span class="passed">create</span> app/views/tasks/show.html.erb <span class="passed">create</span> app/views/tasks/new.html.erb <span class="passed">create</span> app/views/tasks/_form.html.erb <span class="failed">error</span> shoulda [not found] invoke helper <span class="passed">create</span> app/helpers/tasks_helper.rb <span class="failed">error</span> 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.
# 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.
<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.