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.
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.
- Create a new directory for your gem
- Make a lib directory, and put a file inside called
.rb. Make a
@ directory and put a @version.rbinside. In that file, create a module called
@ and put a @VERSIONconstant inside.
- Put a gemspec in your root. You can grab a sample one from my sample gem skeleton repo on github
- Grab my sample Rakefile as well. It will give you a rake build task for your gem.
- You’re probably going to want a
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
ActiveSupport constants, and classes and modules under
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
Applicationsubclass is created, but before the user has an opportunity to configure it yet. This allows you to run code on the
Applicationthat the user can modify.
before_initialize: This hook is run after the user completes all of their configuration (including in the
environmentsdirectory), 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.
i18n: These hooks run after the base class of a particular component is loaded. For instance, the
action_viewhook runs after
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
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
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
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’
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
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.
file_nameare extracted from the name passed in. For instance, if the user passes in
file_path, camelized. For instance,
file_name, humanized. For instance,
file_name, pluralized. For instance,
.. For instance,
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