The Modest Rubyist

A blog about Ruby & Rails from beginner to advanced.

Rails 3 Plugins - Part 3 - Rake Tasks, Generators, Initializers, Oh-My!

ShareThis March 16th 2010

With the basics of Rails::Engine in our toolbox, in part three, I move on to some of the more advanced features of Railties and Engines.

In part two, our Rails::Engine subclass, Authr::Engine, was simply defined like below.

# lib/authr/engine.rb
module Authr
   class Engine < Rails::Engine
       engine_name :authr
   end
end

This may seem sort of useless. As we saw it wasn’t. Simply defining this subclass provides us with robust code-sharing in Rails. Similarly, when subclassing Rails::Railtie, we are given early access to the initialization process of the containg application. This, as I discuss in detail in part one, is what differs the Rails::Railtie from a plugin in vendor/plugins.

There is, however, much more we can do within the class defintion of our Railtie or Engine. Remember Rails::Engine is a subclass of Rails::Railtie, thus inheriting all of its functionality. I will note when something is only available when defining a subclass of Rails::Engine.

Raking in the benifits

So you have some rake tasks that would be useful for a developer using your shiny, new plugin? No problem. With a Rails::Railtie this is easy. Toss all your rake tasks in a file inside your gem, add a few lines to your Railtie definition and your good to go.

I suggest using the convention used by Rails core and place your .rake files in YOUR_RAILTIE_DIR/lib/railties/. You can then have your tasks autoloaded by adding this to your Railite,

rake_tasks do
  load "your_railtie/railties/tasks.rake"
end 

If your following from part two, you could add rake tasks to authr3 like this,

# lib/authr/engine.rb
module Authr
  class Engine < Rails::Engine
      engine_name :authr

      rake_tasks do
        load "authr/railties/tasks.rake"
      end

   end
end

If everything is properly hooked up you should see your tasks defined in tasks.rake listed when you run

 >>rake -T

Generating on Top of Shoulders of a Giant

Thor is a good name for a giant, right? As well as a complete overhaul of plugins, Rails 3 also has an entirely new API for writing generators built on top of Thor. If you are familiar with Thor, which I really wasn’t minus scanning a few tutorials and trying it once, then anything you would normally do with it you can do inside Rails 3 generators. Rails 3 extends Thor’s actions in Rails::Generators::Actions and provides other needed generator functionality through Rails::Generators::Base and several other classes and modules.

In part 2, we created an Account model to represent a user passing through our authentication engine. This is a great example of when we could use a generator to provide developers with a migration for creating the accounts table. Simply dropping a template in db/migrate isn’t enough. Just like in previous versions of Rails, the generator API provides methods for handling special actions like creating migrations with timestamps exactly like those generated by ActiveRecord.

First lets do a little busy work to set up our generator. We need a folder to place all our generators in. Rails 3 looks in each plugin’s lib/generators directory for generators. To create a generator first create a subdirectory in lib/generators. For authr3, I used the same name as the plugin and created the folder lib/generators/authr.

Next we define our generator by subclassing Rails::Generators::Base. In Rails 2, I believe the equivalent class is Rails::Generator::Base (the singular use of Generator is the difference). Following convention, our subclass should be named something like MyGenerator and defined in my_generator.rb in a subdirectory of lib/generators. The basic definition of a generator for authr3 looks like this,

 # lib/generators/authr/authr_generator.rb
 require 'rails/generators'

 class AuthrGenerator < Rails::Generators::Base
   def self.source_root
      @source_root ||= File.join(File.dirname(__FILE__), 'templates')
   end
 end

First, we require what we need. Next we define our generator, AuthrGenerator, as a subclass of Rails::Generators::Base. Finally, we add a class method to our generator, ‘source_root’. This class method must be defined in your generator. Unlike Rails 2 generators, a ‘templates’ subdirectory is not automatically added to the load path. Instead ‘source_root’ must return a path to where your templates are stored. This may seem tedious at first but it gives you flexibility and options if you are writing more than a simple generator. You could for example use this to look up templates in a user’s home directory or elsewhere on the system.

The above is something you will need to do for new generators included with your Railties and Engines. The built-in Rails generators (rails g[enerate]) does generate generator skeletons for you. However, it generates a subclass of Rails::Generators::NamedBase. Rails::Generators::NamedBase differs from Rails::Generators::Base in that it expects one argument to be passed to the generator. I will not be discussing Rails::Generators::NamedBase in further detail in this post. You can, of course use rails generate to create the files you need and simply change your generator to be a subclass of Rails::Generators::Base.

So now that we have the necessary pieces in place, double-check that the generator is listed from within the containing Rails application.

 >>rails g
 . . .
 Authr:
   authr
 . . .

Ok, good to go. Let’s generate a migration. My migration template for authr3 looks something like below. Place the file in lib/generators/authr/templates. If you changed this in AuthrGenerator.source_root then place the file there.

 # lib/generators/authr/templates/migration.rb
 class CreateAccountsTable < ActiveRecord::Migration
   def self.up
     create_table :accounts do |t|
       t.string :uname
       t.string :hashed_password
       t.string :remember_token
       t.datetime :remember_expiry
       #Any additional fields here

       t.timestamps
    end
  end

  def self.down
    drop_table :accounts
  end
end

The details of the migration are not really relavent. I lifted it from my authr3 repository.

Now we must go back to our AuthrGenerator and add the necessary pieces so it can be generated and placed in db/migrate with timestamps and a new file name. Each public instance method defined on the generator class is invoked when the generator is executed. We will define a method ‘create_migration_file’ on AuthrGenerator. You can name this method ‘foo_bar_de_do_da’ if you wish, however.

 # lib/generators/authr/authr_generator.rb
 require 'rails/generators'
 require 'rails/generators/migration'     

 class AuthrGenerator < Rails::Generators::Base
   include Rails::Generators::Migration
   ...
   # Implement the required interface for Rails::Generators::Migration.
   # taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
   def self.next_migration_number(dirname)
     if ActiveRecord::Base.timestamped_migrations
       Time.now.utc.strftime("%Y%m%d%H%M%S")
     else
       "%.3d" % (current_migration_number(dirname) + 1)
     end
   end

   def create_migration_file
     migration_template 'migration.rb', 'db/migrate/create_accounts_table.rb'
   end
 end

The first thing we add is the inclusion of Rails::Generators::Migration defined in rails/generators/migration. This is necessary in order to use the ‘migration_template’ action. The ‘migration_template’ action works pretty similar to its Rails 2 counterpart: expecting two strings a source template and a destination file. It will automatically prepend the proper timestamp to the filename when the generator is invoked. Unlike Rails 2, however, simply calling ‘migration_template’ is not enough. We must also define AuthrGenerator.next_migration_number in order for it to work. Jose Valim has mentioned the possibility ActiveRecord::Generators::Base.next_migration_number will be exposed as a class method, which you can then use explicitly in your generators. As of now, I do not believe this is possible, and you are stuck defining it on your own.

There is a way to get around the nusance of defining ‘next_migration_number’ and shorten up our generator’s code. This option is only available if your migration template is static. Instead of having a template at all we can simply invoke the built-in migration generator. Here is an example Jose Valim presented in a discussion on Google Groups.

  class ActsAsTaggableOnMigrationGenerator < Rails::Generators::Base
    invoke "migration", %(add_fields_to_tags name:string label:string)
  end

Notice invoke is called on the generator class itself not from inside one of its instance methods. The first argument passed is the name of the generator. The second arguments is an array of arguments exactly like the ones you would pass to the command-line rails generate script.

Generators, like other APIs in Rails, are extremely flexible and easy to use. There are many other things you can do with your generators like define command-line arguments a generator can take. Since this post is getting long enough, I will leave some of the Generators API more advanced features for later in this series.

Intialization in your Plugin and Your App

The main difference between Railties and a classic plugin living in vendor/plugins is when they are loaded in the app boot up process and their access to an application’s initialization.

First, say you have some things that need to be setup inside your plugin when the server boots up the application. This is what simple initializers are for. Here is how we can define an initializer in our Raltie.

 class MyRailtie < Rails::Railtie
   ...
   initializer "my_plugin.some_init_task" do |app|
     # app variable represents application object
     # for containing app
   end
 end

The Rails::Railtie.initializer class method is what lets us define our initializer. It takes a string, the name of the initializer and a block. It also excepts some options but more on that in a second. The block is passed one parameter, a reference to the containing application object, Rails.application. Any action you would perform on Rails.application you should be able to perform on the parameter passed to the block.

As I mentioned we can also pass ‘initializer’ some options. The :before and :after options allows us to hook into the application’s init process and specify when our initializer will be called. Ryan Bigg does a great job of detailing all of the available initialization points our plugin can hook into. Here is a quick example of setting our initializer to run after load paths have been set in the application.

 initializer "my_plugin.another_task", :after => :set_load_path do |app|
    #init stuff here
 end

When doing this, it is important to require what you need and include whatever file in the rails codebase that defines the initializer you are using. In the case of set_load_path we would require ‘rails/engine’. Look to Ryan Bigg’s guides for what initializer is defined where.

That’s it for part three. Hope this series continues to be helpful for people. I am pretty sure I will be writing one final part, addressing some of more advanced points and nuances.

blog comments powered by Disqus