Writing rails plugins - tips and tricks - plugins with generators
Following the sequence I started here, but changing the proposed order, let’s talk a little about Rails Generators.
Generators are one of the coolest things in Rails!
Of course you have already seem them, remember the first screencast you’ve seen about rails? Yes, that one where the guy wrote an entire CRUD application in two minutes …
Generators are a way to, generate code
But the generated code can be what you want, and rails already has a great support for writing generators, because it is already used in the rails core.
OK, but why would you want to write a generator?
Think about the project that is starting today, remember all that CRUD forms you’ll need to create, or that almost equal start point for every page of the application …
Now think about all the applications for your company …
Got it?
Ok, I agree that “scaffolding” a generic CRUD is not really useful, but if that scaffold is done following your company standards, it can improve a lot the development performance …
Now, I guess you are thinking: Good, generators seem cool, but stop the easy talk e show me the code!
So, let’s play a little …
Wi’ll start creating a new rails project: rails plugins102
Now from the project directory run: script\generate plugin my_generator
(if you think this is kind of a flash back, maybe you read my previous post about creating rails plugins
)
Now that you have already created your brand new plugin, let’s write some code!
To create a generator you need to create a generators directory inside your plugin’s directory, a directory with the name you want for your generator (for example test_gen), and a templates directory inside the previous one, for example, it will look like this:
- my_generator
- lib
- tasks
- test
- generators
- test_gen
- templates
- test_gen
Ok, you do not really need a plugin to create a generator, you can of course, place your brand new generator inside the following directories:
- RAILS_ROOT/lib/generators
- RAILS_ROOT/vendor/generators
- RAILS_ROOT/vendor/plugins/plugin_name/generators
- USER_HOME/.rails/generators
- gems ending in _generator
But I think that a plugin is the easiest way to do it, and it will be easier to deploy to your applications too.
Of course that a GEM would be a better way, because you can install only once per machine, and does not need it installed on the server, but I have never created a GEM before, so let’s stay with the plugin for a wile …
to start coding the generator, we’ll need to create a file named [generator_name]_generator.rb inside the generator’s directory, in my case, I’ve created the file: my_generator/generators/test_gen/test_gen_generator.rb
Now inside that file, create the class to define your generator, it must be named according to the file name, in my case TestGenGenerator, and this class must extend one of the base classes for Rails generators:
Rails::Generator::Base or Rails::Generator::NamedBase, I’ll use NamedBase (The base for the controller generator), and I’ll create a generator for a model and a migration, just to show how to do it …
The NamedBase is the perfect base class for generators that expect parameters in the form: Name [param1] [param2] …
For all the others, Base is a best start point …
The starting point for our code is:
1 2 3 4 5 6 7 | class TestGenGenerator < Rails::Generator::NamedBase def manifest record do |m| end end end |
In this class, all we need to do is to set up the generator’s manifest (more on this later) and to setup any local variables to be used by our templates (more on this later too).
with only this, we can already run: ruby script\generate test_gen asdas_dasda asd:ash (randon letters as parameters for now).
The NamedBase will automatically set up some variables for us, and the values for the given parameters will be:
- class_name -> AsdasDasda
- class_nesting ->
- class_nesting_depth -> 0
- class_path ->
- file_path -> asdas_dasda
- name -> asdas_dasda
- plural_name -> asdas_dasdas
- singular_name -> asdas_dasda
- table_name -> asdas_dasdas
- attributes -> #<Rails::Generator::GeneratedAttribute:0×3716418>
- args -> asd:ash
of course this generator now will generate nothing, because our manifest is empty, so let’s build a simple example to see how it really works …
so, lets create inside the template directory, a directory called “dummy”, and a blank file called “log.log” inside of it and let’s change the manifest method to some thing like this:
1 2 3 4 5 | def manifest record do |m| m.file 'dummy/log.log', "log/#{file_path}.log" end end |
This code will tell the generator, to copy our newly created, blank file, to $APP_ROOT/log/asdas_dasda.log if we run the generator with the same parameters as before …
but just copying files from one place to another is not a very cool thing to be done, so let’s play a little with ERB, and let’s create a migration for our plugin, so we need to change again the manifest method as follow:
1 2 3 4 5 6 7 8 | def manifest @migration_name = "Create#{class_name}" @migration_action = "add" record do |m| m.file 'dummy/log.log', "log/#{file_path}.log" m.migration_template 'lib/mymigration.rb',"db/migrate", :migration_file_name => "create_#{file_path}" end end |
As you can see in the code, I’m telling the manifest that I have a template named mymigration.rb inside the directory lib in my templates directory, it will be processed and the result will be placed inside the directory “db/migrate” and will be called “000_create_asdas_dasda.rb” (the 000 will be replaced with the latest migration number plus one as the normal “generate migration” command do.
to be able to access the attributes migration_name and migration_action in the template, we need create the accessor methods for them, it is as easy as adding the following line to the TestGenGenerator class (outside the manifest method):
1 | attr_accessor :migration_name, :migration_action |
My migration template is the following:
1 2 3 4 5 6 7 8 9 10 11 | class <%= migration_name.underscore.camelize %> < ActiveRecord::Migration def self.up<% attributes.each do |attribute| %> <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%> <%- end %> end def self.down<% attributes.reverse.each do |attribute| %> <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%> <%- end %> end end |
It is an ERB template that will generate a ruby file.
Now we have already a very cool working plugin, but what else can we do?
to answer this question, I’ll quote a little the documentation of the Rails::Generator::Commands::Create class, that means, what can you do within the manifest method:
- class_collisions - Check whether the given class names are already taken by Ruby or Rails. In the future, expand to check other namespaces such as the rest of the user‘s app.
- directory - Create a directory including any missing parent directories. Always directories which exist.
- file - Copy a file from source to destination with collision checking.
- identical? - Checks if the source and the destination file are identical. If passed a block then the source file is a template that needs to first be evaluated before being compared to the destination.
- migration_template - When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
- readme - Display a README.
- route_resources - add a route to the routes.rb
- template - Generate a file for a Rails application using an ERuby template. Looks up and evaluates a template by name and writes the result.
I think that is it, you can ask more questions if you want, I’ll try to answer all, if any
for more resources on Rails generators you can follow this links:
http://wiki.rubyonrails.org/rails/pages/UnderstandingGenerators
http://www.aidanf.net/node/33
http://api.rubyonrails.org/classes/Rails/Generator/Base.html
http://api.rubyonrails.org/classes/Rails/Generator/NamedBase.html
http://api.rubyonrails.org/classes/Rails/Generator/Commands/Create.html
I hope this little step by step help some one!
The next one will be about writing tests for your plugins! and the fourth I have not started to think about yet ![]()
If you enjoyed this post, make sure you subscribe to my RSS feed!





I get errors when I try following these steps and running the generator.
First of all, I get “undefined local variable or method `file’ for #”, and if I comment out the m.file line that goes away.
Then, I get “undefined local variable or method `migration_name’ for #”. If I change all the references to migration_name and migration_action in the template to be instance variables, which I expected them to be, the error becomes “You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.underscore”, and the generated migration file is empty.
Sorry, it was my mistake, I forgot to add the following line in the text:
to be able to access the attributes migration_name and migration_action in the template, we need create the accessor methods for them, it is as easy as adding the following line to the TestGenGenerator class:
Worked perfectly, thanks
Very nice
btw very good tips i will use them.
Very useful information
I see there are realy good tips. I am going to use some of them
I am trying to customize rails 2.0.x scaffold generator then come across this blog. I wonder if you know I could extend the feature in Rails::Generator::Commands::Create within the generator to add additional method (such as namespaces based route generation) ? Sample code would be very appreciated.
Amazing. Thank you so much for so greate tutorial
Nicolas
Hi,
Thank you so much for this tutorial.
I work with rails for 6 months and I have a question, I’ve never found answer.
I would like to run a “generate” from a rails application.
For example, I would like to call “ruby script\generate test_gen asdas_dasda asd:ash” but from a controller. It means generating files with template and associate this action with an action of one of my controller
Thank you for any help
Nicolas B
This is really good post for plugin generator, I am searching for same kind of post..now got it ..thanks!
[…] […]