hone.heroku.com

Rails 2.3 and Bundler

categories: rails bundler

02 Feb 2010

Here at OtherInbox, we’ve had some issues dealing with gem dependencies using config.gem. Luckily, Carl Lerche and Yehuda Katz have made Bundler for us. Bundler has great dependency resolution and just works™. They’re also smoothing out deployment workflow issues.

Getting Setup with Rails 2.3.x

Setting up Bundler on Rails 2.3.x is fairly straightforward. There’s only 3 steps.

1. setup the Gemfile
2. setup the preinitializer.rb
3. monkey patch the boot.rb

Setup the Gemfile

The Gemfile is the manifest file that lists what gems are part of the application and what environment they belong in. Bundler defaults the gems in vendor/gems/ and the binaries in bin/. Unfortunately, vendor/gems/ is special to rails, so you need to specify a different directoy to use: bundle_path "vendor/bundler_gems". Specifying a gem is as easy as this:

gem "<gem name>", <options>

The options are well, optional. You can specify the version number. You can also specify what to require if the lib doesn’t match gem name. You will need to do this for gems from github, since they’re prefixed with github usernames. There’s also a :only and :except options for specifying which environments the gem should be in. See the following example:

gem 'mislav-will_paginate', '2.3.8', :require_as => 'will_paginate', :except => :test

You can use source to specify different gem sources outside of rubyforge and gemcutter . For instance, I have

source "http://gems.github.com"

in my Gemfile to use github.

You use only and except blocks to specify multiple gems for a particular staging environment.

Here’s my example Gemfile for herocutter:

bundle_path "vendor/bundler_gems"

gem 'rails',      '2.3.5'
gem 'postgres'
gem 'clearance',  '0.8.4'
gem 'gravtastic', '2.2.0'
gem 'formtastic', '0.9.7'
gem 'git',        '1.2.5'
gem 'pacecar',    '1.2.0'

only :test do
  gem 'factory_girl',     '1.2.3'
  gem 'cucumber',         '0.6.1'
  gem 'cucumber-rails',   '0.2.3'
  gem 'webrat',           '0.6.0'
  gem 'database_cleaner', '0.4.2'
  gem 'shoulda',          '2.10.2'
  gem 'rr',		          '0.10.5'
  gem 'redgreen'
end

only :development do
  gem 'factory_girl',     '1.2.3'
  gem 'n',                '0.1.0'
end

Don’t forget to remove the gems from the vendor/gems/ directory and the config.gem lines from your environment files.

Caveat

If you use disable_system_gems to not search your system gems, it will not work on heroku since their system is dependent on a system install version of thin.

In a consulting project when I’ve made the switch to bundler, all monkey patched gems I’ve kept those inside vendor/gems/ as well as the appropriate config.gem lines in the environment files. If you need to require their dependencies in bundler, rails won’t load the depencies properly when accessed in the lib/ directory. For instance, in a monkey patch to lockdown I used ParseTree and had to require that explicitly in lib/lockdown/init.rb.

Setup the preinitializer.rb

This step is simple. In your config/preinitializer.rb, you need to get rails to load bundler in the path by adding:

require "#{File.dirname(__FILE__)}/../vendor/bundler_gems/environment"
Caveat

In Yehuda’s blog post, insert the environment loading code into the config/preinitializer.rb. I couldn’t get it to work quite right.

Monkey Patch the boot.rb

Instead of putting loading of the environment gems in config/preinitializer.rb, I found it works better to monkey patch config/boot.rb directly (got this from the gemcutter source). We basically need to tell rails to load the right environment gems. You can do this by adding this to the bottom of config/boot.rb right before the Rails.boot!:

class Rails::Boot
  def run
    load_initializer
    extend_environment
    Rails::Initializer.run(:set_load_path)
  end
 
  def extend_environment
    Rails::Initializer.class_eval do
      old_load = instance_method(:load_environment)
      define_method(:load_environment) do
        Bundler.require_env RAILS_ENV
        old_load.bind(self).call
      end
    end
  end
end

You can also refer to the config/boot.rb on herocutter to see what I changed exactly.

Version Control

You can ignore the vendor/bundler_gems/ directory since it gets created when running the gem bundle command.


blog comments powered by Disqus