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