boldr by Nicolas Mérouze

How to upgrade plugins to Rails 3.0

Rails 3.0 beta is out and it's now time to upgrade all the plugins available. To show you how to do it I've decided to create a small plugin compatible with Rails 2.x and Rails 3.0. It's a wrapper around Rack::Cache to insert it automatically in a Rails application.

It's a gem

First things first we create the gem we will name rails-cache. Thanks to Jeweler it's dead easy. Just a few lines of code in a Rakefile and it's done:

require "rake"

begin
  require "jeweler"
  Jeweler::Tasks.new do |gem|
    gem.name = "rails-cache"
    gem.summary = "Add Rack::Cache to your Rails app"
    gem.email = "nicolas.merouze@gmail.com"
    gem.homepage = "http://github.com/nmerouze/rails-cache"
    gem.authors = ["Nicolas Mérouze"]
    gem.files = Dir["*", "{lib}/**/*"]
    gem.add_dependency("rack-cache", "~> 0.5.2")
  end

  Jeweler::GemcutterTasks.new
rescue LoadError
  puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
end

To create the VERSION file a echo "1.0.0" > VERSION is enough.

Rails 2.x way

The code of the gem will be in lib/rails/cache.rb and it's just the insertion of the Rack::Cache middleware:

require "rack/cache"

Rails.configuration.middleware.use Rack::Cache, 
  :metastore   => "file:#{Rails.root + "cache/rack/meta"}",
  :entitystore => "file:#{Rails.root + "cache/rack/body"}",
  :verbose     => true

We build the gem with rake build and we install it with gem install --local pkg/rails-cache-1.0.0.gem. Then we need to load it in our environment.rb:

RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.gem "rails-cache", :lib => "rails/cache"
end

Rack::Cache is now plugged with the Rails application!

The new way

Rails 3.0 comes with Bundler. A cool thing with Bundler is that you can load a gem which have a custom path:

gem "rails-cache", :require => "rails/cache", :path => "../rails-cache"

The :path option is the path to our gem. We have a lot of solution to do the same in Rails 2.x, with a plugin instead of a gem for example, but it is not as elegant as with Bundler (Bundler is usable with Rails 2.x).

To create a plugin for Rails there's an object for that now, Rails::Railtie. So we just need to write an object which inherit from Rails::Railtie in lib/rails/cache.rb:

require "rack/cache"

module Cache
  class Railtie < Rails::Railtie
    railtie_name :rails_cache

    initializer "rails_cache.insert_rack_cache" do |app|
      app.config.middleware.use Rack::Cache,
        :metastore   => "file:#{Rails.root + "cache/rack/meta"}",
        :entitystore => "file:#{Rails.root + "cache/rack/body"}",
        :verbose     => true
    end
  end
end

The gem has been upgraded to Rails 3.0!

Rails 2.x and 3.0 in a gem

This is just an example here, but it's possible there's a better solution:

require "rack/cache"

if Rails::VERSION::MAJOR == 2
  Rails.configuration.middleware.use Rack::Cache, 
    :metastore   => "file:#{Rails.root + "cache/rack/meta"}",
    :entitystore => "file:#{Rails.root + "cache/rack/body"}",
    :verbose     => true
else
  module Cache
    class Railtie < Rails::Railtie
      railtie_name :rails_cache

      initializer "rails_cache.insert_rack_cache" do |app|
        app.config.middleware.use Rack::Cache,
          :metastore   => "file:#{Rails.root + "cache/rack/meta"}",
          :entitystore => "file:#{Rails.root + "cache/rack/body"}",
          :verbose     => true
      end
    end
  end
end

And you can find the whole code on GitHub.

More about Plugins/Engines

If you liked the article and/or want to talk to me about it, you can follow me on twitter.