Creating your own generators in rails

In this article I will show how to create a generator that generates the code from my previous article about basic user authentication in rails.

Rails generators are used to automatically generate code. You probably already use them to generate controllers, models and migrations. Every time you call script/generate you’re using a generator. However it’s easy to create your own generator in rails. If you find yourself copying code between applications you may find it useful to create a generator to generate the code for you.

Note: If your code is suitable for packaging as a plugin that is probably a better way to share the code between applications. However there are some cases when a generator is more suitable. Generators are useful for generating repetitive boiler-plate code or starting-point code that will be heavily modified. In this example the generator can be used to generate a basic authentication system for your application that you can then modify to suit the particular needs of your application.

As is usual in rails, creating a generator follows certain conventions. Create a folder for the generator (we call call this folder basic_auth). In this folder there should be a directory called templates. This directory holds all the code templates for our code generator. You can organize the files within this directory in whatever way you want. There should also be files called USAGE and basic_auth_generator.rb. The contents of the USAGE file are printed automatically when the user calls the generator with -h or —help as an argument.

The file basic_auth_generator.rb is the main file for the generator and contains a list of instructions for generating the target code. The full directory structure for the basic authentication generator code is:

basic_auth
|-- USAGE
|-- basic_auth_generator.rb
`-- templates
    |-- INSTALL
    |-- controllers
    |   `-- user_controller.rb
    |-- lib
    |   `-- basic_auth.rb
    |-- migrate
    |   `-- create_users.rb
    |-- models
    |   |-- notifications.rb
    |   `-- user.rb
    |-- test
    |   |-- fixtures
    |   |   `-- users.yml
    |   |-- functional
    |   |   `-- user_controller_test.rb
    |   `-- unit
    |       `-- user_test.rb
    `-- views
        |-- notifications
        |   `-- forgot_password.rhtml
        `-- user
            |-- change_password.rhtml
            |-- forgot_password.rhtml
            |-- hidden.rhtml
            |-- login.rhtml
            |-- signup.rhtml
            `-- welcome.rhtml


To create a generator we extend Rails::Generator::Base and define a manifest method which lists all the steps to take to generate the desired code. Here is the listing from basic_auth_generator.rb

class BasicAuthGenerator < Rails::Generator::Base
  def manifest
    record do |m|

      # Controller
      m.file "lib/basic_auth.rb", "lib/basic_auth.rb"
      m.file "controllers/user_controller.rb", "app/controllers/user_controller.rb" 

      # Models
      m.file "models/user.rb", "app/models/user.rb"
      m.file "models/notifications.rb", "app/models/notifications.rb"

      # Tests
      m.file "test/unit/user_test.rb", "test/unit/user_test.rb"
      m.file "test/functional/user_controller_test.rb", "test/functional/user_controller_test.rb"
      m.file "test/fixtures/users.yml", "test/fixtures/users.yml"

      # Views. 
      m.directory "app/views/notifications"
      m.directory "app/views/user"
      m.file "views/user/login.rhtml", "app/views/user/login.rhtml"
      m.file "views/user/signup.rhtml", "app/views/user/signup.rhtml"
      m.file "views/user/change_password.rhtml", "app/views/user/change_password.rhtml"
      m.file "views/user/forgot_password.rhtml", "app/views/user/forgot_password.rhtml"
      m.file "views/user/hidden.rhtml", "app/views/user/hidden.rhtml"
      m.file "views/user/hidden.rhtml", "app/views/user/welcome.rhtml"

      m.file "views/notifications/forgot_password.rhtml", "app/views/notifications/forgot_password.rhtml"


      m.migration_template "migrate/create_users.rb", "db/migrate"

      m.readme "INSTALL"
    end
  end

  def file_name
    "create_users"
  end

end

The basic_auth_generator is very simple. All it does it copy each file from the template directory to its correct destination. m.file copies the source file from the generator’s templates directory to the specified destination in the application that calls the generator. m.directory creates a new directory. m.migration_template creates a new migration. For this method we give it the source of the migration in the templates directory and a destination migration directory. It will choose an appropriate filename based on the migrations that already exist (m.migration uses the file_name method so we define it here to return the string “create_users”. We wouldn’t need to do this if we extended NamedBase - see below). m.readme prints the contents of a file. We use this to give the user further instructions after the generator is finished.

There is one change to the code from the previous authentication article. There are a couple of methods that we need to appear in our application controller. However we don’t want to overwrite the file application.rb as there may already be other code in it. We could print a message asking the user to paste the required code into application_controller.rb after generation. Instead we move these methods into their own module. We copy this module into the lib directory and print a message after generation asking the user to include them.

So the code destined for application.rb goes into lib/basic_auth.rb.

module BasicAuth

  def login_required
    ...
  end

  def current_user
    ...
  end

  def redirect_to_stored
    ...
  end  

end

and the user has to include this module in their application_controller.rb:

require_dependency "basic_auth"

class ApplicationController < ActionController::Base
  include BasicAuth
end

We print out an instruction to do this by putting it in the file INSTALL and calling m.readme "INSTALL" at the end of the manifest.

Going further

There is not much to this generator as it just uses the file method to copy files without modifying them. For more complex generators we can extend Rails::Generator::NamedBase instead of Rails::Generator::Base. This allows us to pass a name to the generator. We can use the template method to generate code based on the name passed to the generator. m.template passes the source file through erb and then copies the result to the destination file. Thus using the template method you can execute ruby code and use the results in your generated template.

As an example you might want to allow the user to specify the name of the controller to generate. If the generator is called using ruby script/generate Account, you can access the name using the methods class_name (returns “Account”) and file_name (returns “account”). You would then define your controller template using

class <%= class_name %>Controller < ApplicationController...

and in your manifest

m.template "controllers/user_controller.rb", "app/controllers/#{file_name}_controller.rb"

To learn how to do more complex things than just copying files have a look at the source for some of the available generators such as login_generator (run gem search -r generator to get a list of available generators) .

Where to put the generator

To use your generator across multiple applications you should put the generator directory somewhere that rails looks for generators such as ~/.rails/generators in your home directory or lib/generators in your application directory.

Using the generator

> ruby script/generate basic_auth
      create  lib/basic_auth.rb
      create  app/controllers/user_controller.rb
      create  app/models/user.rb
      create  app/models/notifications.rb
      create  test/unit/user_test.rb
      create  test/functional/user_controller_test.rb
      create  test/fixtures/users.yml
      create  app/views/notifications
      create  app/views/user
      create  app/views/user/login.rhtml
      create  app/views/user/signup.rhtml
      create  app/views/user/change_password.rhtml
      create  app/views/user/forgot_password.rhtml
      create  app/views/user/hidden.rhtml
      create  app/views/user/welcome.rhtml
      create  app/views/notifications/forgot_password.rhtml
      create  db/migrate
      create  db/migrate/001_create_users.rb
      readme  INSTALL

Download

You can download the generator here: basic_auth.tar.gz, basic_auth.zip