Ruby on Rails
UnderstandingGenerators

What are they?

Generators are little machines that spit out Ruby code. Usually they do only one thing, and manage that well.

Generators help reduce the tedium of dealing with boilerplate code by generating it for you. Generators can produce simple code (such as for a new model) or more complex (such as a login system).

See also:

How a generator works

A Generator’s files and directory tree

A generator consists of the following directory and files:

  ~/.rails/generators/[generator_name]
     USAGE
     [generator_name]_generator.rb
     /templates
        [any template files, named appropriately]

Windows 2000/XP Users: create this directory structure in the root of your users “Documents and Settings” directory. For example:

  C:\Documents and Settings\[username]\.rails\generators\[generator_name]
    USAGE
    [generator_name]_generator.rb
    /templates
       [any tempalte files, named appropriately]

Windows XP does not allow directory names beginning with a period e.g. ”.rails” is not acceptable. How then may I create a directory for my own generators?
Simply use the command line to navigate to the Documents and settings\username directory and “mkdir .rails” without the qoutes.

The USAGE file is a text file describing (for the command line) how to use the generator.

Template files in the /templates directory are eRb files just like a Rails view file (.rhtml). In other words, they consist of a mixture of plain text and ruby code that act as an intermediate step to create a final plain text file—the generated text files. They have no special extension, therefore, since they are named according to what makes sense. For example, model.rb is a template file used by the model generator. It is renamed by the generator code to become your shiny new model, e.g. user.rb.

Modify existing generators

Is there any support for “overriding” the generated scripts? In one case, I’d like to generate an additional file, in one case, I’d like to put a generated file in a different location, and in one case, I’d like to actually affect the content of the generated code.

From Jeremy Kemper:
Sure, adapt one of the builtin generators to your needs. Copy the files from the gem directory or from a svn checkout to ~/.rails/generators. For example:


  cd /path/to/svn/checkout
  cp -r railties/lib/rails_generator/generators/components/model ~/.rails/generators/mymodel

Then alter your new mymodel generator as you like. Run it just as you would with the builtin generators using:


  ./script/generate mymodel Foo
  1. RAILS_ROOT/lib/generators
  2. RAILS_ROOT/vendor/generators
  3. RAILS_ROOT/vendor/plugins/plugin_name/generators
  4. USER_HOME/.rails/generators,
  5. gems ending in _generator
  6. built-in ones (inside rails gem)

You can even create a generator with the same name as a builtin to override it.

This is a great way quickly roll code in your own style. Modifying the scaffold generator is particularly rewarding.

Create your own generator

Why?

It’s an easy way to reduce repetition when you build many projects with similar functionality. For example, if you have a favorite stylesheet that does some CSS magic buttons, you could create a generator to do that. Here’s an example of how I (canadaduane )built a CSS-only 3-state tabbed navigation bar generator.

How?

Your generator should inherit from one of two classes:

The NamedBase class has everything the Base class has, plus it assumes your generator takes a single class name as the first parameter, followed by a list of “actions” (or “args”, which is exactly the same thing if you look at the NamedBase? class).

In the tabbed navigation bar generator (tabbed_navbar), I chose to use Rails::Generator::Base because this generator does not follow the pattern of needing command-line parameters of the form:

   [one-special-arg] [arg1] [arg2] [arg3] ...

For example, the controller generator expects a [controller name] as its first argument, followed by a list of [actions]. If your generator follows that pattern, choose NamedBase?; otherwise, choose Base.

Now, let’s get in to the generator code. Here’s a breakdown of tabbed_navbar_generator.rb (full source code available at TabbedNavbarGenerator ):

class TabbedNavbarGenerator < Rails::Generator::Base
  attr_accessor :height_in_pixels, :width_in_pixels, :images

Anything that you make “accessible” (attr_reader or attr_accessor please see Attr Macros for more info) in your generator class will be available to in your eRb-driven template files. For example, the height_in_pixels instance variable can be accessed with the usual <= height_in_pixels > eRb code.

  def initialize(*runtime_args)
    super(*runtime_args)
    @images = args.collect { |i| Image.new(i) }

    # Height of the Tabbed navbar is the height of any image in that bar / 3
    @height_in_pixels = @images[0].height / 3
    # Sum of each tab's width
    @width_in_pixels = @images.inject(0) { |sum, img| sum + img.width }
  end

The initialize method will be passed a list of arguments given at runtime. If you just pass them on to the Base class’s initialize method, you’ll get all of the standard functionality of generators (for example, users can—force files to be written even if they already exist), and the optional actions or arguments that it doesn’t take care of will be passed back in the @args variable.

So in the case of this tabbed navbar generator, args (or @args) is a list of image filenames passed in through the command line.

Next: The Manifest Method

Your generator class must contain a manifest method which will create appropriate directories and files, and copy over your template files to the correct locations.

As an example, here is a manifest method from the tabbed navbar generator:

  def manifest
    record do |m|
      # Stylesheet and public directories.
      m.directory File.join('public/stylesheets')
      m.directory File.join('app/views')

      # Model class, unit test, and fixtures.
      m.template 'tabbed_navbar.css',   File.join('public/stylesheets', "tabbed_navbar.css")
      m.template '_tabbed_navbar.rhtml',    File.join('app/views', "_tabbed_navbar.rhtml")
    end
  end

The m.directory and m.template methods are handy features of the Generator::Base class. They allow you to easily create directories and files and they give the user the option of overriding (or not) if files already exist. That way, your generator won’t clobber any files with the same names without the user’s permission.

File.join is simply a way of joining strings together using the default system directory delimeter (”/” on Unix, ”\” on windows).

You can also override the banner method to create your own one-line usage banner:

  protected
    # Override with your own usage banner.
    def banner
      "Usage: #{$0} #{spec.name} [WIDTHxHEIGHT:image1 [WIDTHxHEIGHT:image2 [ ... ]]]" 
    end

If you would like to see the full source of the Image class for this generator, click here: TabbedNavbarGenerator

category: Understanding, Stub