Rails Dispatch

Rails news delivered fresh

Presented by Engine Yard

This week, we’ll be covering the first of two parts on the relatively advanced topic on how to use the new plugins APIs in Rails 3 to extend the framework itself. In this first part, we’ll cover how to create a Rails plugin inside a gem, what environment you can expect when Rails loads your plugin, how to create your own generators, and add your own rake tasks.

Next week, we’ll finish up generators by covering overriding default Rails generators like controller or scaffold. Next, we’ll cover the new ActionController API, which makes it dead-easy to implement components in Rails, like the cells plugin. Finally, we’ll show how you can hook non-Active Record models into Action Pack with all of the tight integration you’ve come to expect from Rails. We won’t have a video this week, but next week I’ll be showing off some of the best plugins that have taken advantage of all of this infrastructure.

Choices

Before we begin, a word on choices. Rails has always prided itself on having a fantastic, well-integrated stack suited for learning Rails and taking it through a period of rapid development followed by a longer period of mature development. Over the years, people have created mechanisms for replacing some of those defaults. Two of the most popular examples, Haml and RSpec, replace key parts of the Rails stack, but don’t change the overall architecture.

In order to achieve the kind of integration that ERB and Test::Unit have with Rails, those plugins leveraged the dynamism of Ruby to extend parts of the Rails framework that the core team didn’t necessarily expect others to extend. Over time, Rails has made it easier to extend the most common of these. Rails 3 continues this tradition in a big way, learning from the universe of Rails plugins while we refactored the core of Rails.

However, when starting with Rails, or helping a friend get started, keep in mind that the Rails core team feels strongly that the widely used, widely integrated defaults provide the best experience. Rails 3 makes it easier to make choices, and you should take advantage of those capabilities when you experience true issues with one of the defaults, or if, as an advanced user, you’ve come to appreciate a somewhat different version of the default stack.

The last thing a Rails developer wants is to have to spend a week at the beginning of a new project thinking about which choices to make. Here’s a good rule of thumb: if you’re not sure, use the defaults.

Rails 3 Plugin Structure: It’s Just a Gem

With that said, let’s dive into how you should structure your Rails 3 plugin.

First of all, Rails 3 plugins packaged as gems have significantly more power than plugins packaged as traditional vendor/plugins. Additionally, gems allow you to specify dependencies, version your code, and make them available through the well-understood Rubygems infrastructure. Creating a gem is quite simple, and I’ll take an entire future week on Rails Dispatch to cover setting up a new gem.

For now, you’ll want to do a few simple things. These steps could be easily automated, but I’m laying them bare so you have a sense of the structure of a gem.

  1. Create a new directory for your gem
  2. Make a lib directory, and put a file inside called .rb. Make a @ directory and put a @version.rb inside. In that file, create a module called @ and put a @VERSION constant inside.
  3. Put a gemspec in your root. You can grab a sample one from my sample gem skeleton repo on github
  4. Grab my sample Rakefile as well. It will give you a rake build task for your gem.
  5. You’re probably going to want a LICENSE, README, and tests. Follow along at my repository or use whatever choices for these things that you like, if you have a strong opinion.

How Rails Calls Your Gem

By default, Rails will require the .rb file you created above extremely early. That means that while you can see the Rails and ActiveSupport constants, and classes and modules under Rails and ActiveSupport, you will not see the other frameworks, the user’s configuration, or the user’s classes.

This gives you the ability to write code that will run before the user takes any action. For instance, you can set default configurations or define generator defaults that the user can override. In order to specify code that Rails should run later on, Rails provides a number of hooks that you can use.

Before we explain how to use the hooks, here they are:

  • before_configuration: This hook is run after the user’s Application subclass is created, but before the user has an opportunity to configure it yet. This allows you to run code on the Application that the user can modify.
  • before_initialize: This hook is run after the user completes all of their configuration (including in the environments directory), but before any initialization occurs.
  • before_eager_load: This hook is run before Rails requires any of the application’s classes. In development mode, eager load never occurs, but this hook provides a mechanism to run code at the last moment that you can be sure no application code is loaded.
  • after_initialize: This hook is run after Rails completes its initialization, and all of the user’s initializers have a chance to run. This allows you to run code after initialization but before any requests.
  • action_controller, action_view, active_record, action_mailer, i18n: These hooks run after the base class of a particular component is loaded. For instance, the action_view hook runs after ActionView::Base is loaded.

To register a callback for one of these, use ActiveSupport.on_load(:hook_name). For instance, the Haml gem needs to run the Haml.init_rails method after both the application and ActionView have loaded. In order to achieve this, they can use the following syntax:

ActiveSupport.on_load(:action_view) do
    ActiveSupport.on_load(:after_initialize) do
      Haml.init_rails(binding)
    end
  end

Extending a Rails Framework

Rails plugins most commonly include a module into a built-in Rails base class. To do this, use the same syntax we used above:

ActiveSupport.on_load(:action_view) do
    include ExtraHelpers
  end

This will defer including the ExtraHelpers until ActionView loads, allowing you to specify that you want to modify a built-in Rails class without worrying about when it is actually loaded. This same syntax works for the other frameworks: you can include modules in an :active_record callback to include them on ActiveRecord::Base, in an :action_mailer callback to include them on ActionMailer::Base, and so on.

Adding Your Own Rake Tasks

In order to add your own Rake task, you will first need to create a subclass of the Rails::Railtie class. This class is more robust than the simple hooks I showed above, allowing you to control Rails’ generators, tell Rails about Rake tasks, set up middleware, add keys to the configuration object, among other things.

Create your Railtie in .rb, so that Rails sees it when it requires that file. Alternatively, if you put a larger Railtie in a separate file, make sure to require it from your main file.

module MyGem
  class Railtie < ::Rails::Railtie
  end
end

It doesn’t matter what you name your Railtie, but you should probably namespace it inside of the module used in your gem, so you don’t use the same name as some other plugin.

Once you have your Railtie, you can use it to provide some code that Rails should run when the user runs the rake command.

module MyGem
  class Railtie < ::Rails::Railtie
    rake_tasks do
      desc "Talk about being in my_gem"
      task :my_gem do
        puts "You're in my_gem"
      end
    end
  end
end

If you have a simple Rake task, including it inline like this will work, but you will usually want to include your tasks in a different file, and load them in. Take a look at the ActiveRecord Railtie.

module ActiveRecord
  class Railtie < Rails::Railtie
    rake_tasks do
      load "active_record/railties/databases.rake"
    end
  end
end

If you add Rake tasks in this manner, the application will have access to them when the user executes rake. These tasks also have access to the normal Rails Rake tasks. For instance, a custom Rake task provided in this manner can depend on Rails’ environment task.

Adding a Custom Generator

When you add a new generator to Rails, you make it available to the rails generate command. It will also show up in the list of available generators.

To add a generator, first create a new generators directory under your lib directory. Next, create a file named _generator.rb. In that file, create a class named MyGem::Generator. Additionally, you’ll want to create a directory called templates under the generators directory.

module MyGem
  class InstallGenerator < Rails::Generators::Base
    source_root File.expand_path("../templates", __FILE__)

    # all public methods in here will be run in order
    def add_my_initializer
      template "initializer.rb", "config/initializers/my_gem_initializer.rb"
    end
  end
end
Rails.application.configure do
  # Uncomment this to turn on verbose mode
  # config.my_gem.verbose = true
end

Because you called your generator MyGem::InstallGenerator, your users will invoke it as rails generate my_gem:install. The generator system relies on naming conventions, so it’s important that you place your generators in the right directories and files, and use the right class name.

If you have generators like rails g controller my_controller, you will want to use Rails::Generators::NamedBase. If you inherit from NamedBase, you will have access to several inflections of the name passed in both in the body of your generator class, as well as in your templates.

  • class_path and file_name are extracted from the name passed in. For instance, if the user passes in admin/posts, the class_path is admin and the file_name is posts
  • file_path is the class_path and file_name joined by /
  • class_name is the file_path, camelized. For instance, admin/posts becomes Admin::Posts
  • human_name is the file_name, humanized. For instance, admin_posts would become Admin posts
  • plural_name is the file_name, pluralized. For instance, post becomes posts
  • i18n_scope is the file_path, with / replaced with .. For instance, admin/posts becomes admin.posts

There are a large number of additional features in the generator system: it’s powerful enough to be used for all of the built-in Rails generators. You can find out a lot more at the Rails Generator Guide. Next week, we’ll pick up where we left off by showing you how you can replace the generators in a default Rails component (like the template engine). This allows you to develop tightly integrated alternatives to Rails defaults, like Haml, DataMapper, and RSpec.