• Sales: (866) 518-YARD

Using the Rubygems Bundler for Your App

By Sam Merritt | November 12th, 2009 at 11:11AM

The new Rubygems bundler makes managing your application’s gem dependencies easy. And for applications with many components, it makes separating components’ dependencies easy too.

Let’s start off with a simple, two-part application. Part 1 is a Sinatra app that puts JSON-serialized messages into an AMQP queue. Part 2 is a daemon that consumes those messages.

Here’s what a Gemfile for this application might look like:

gem 'json'
gem 'sinatra'
source 'http://gems.github.com'
gem 'famoseagle-carrot', :require_as => 'carrot'

gem 'eventmachine'
gem 'amqp'

The Sinatra app starts off with these lines:

# This makes sure the bundled gems are in our $LOAD_PATH
require File.expand_path(File.join(File.dirname(__FILE__), 'vendor', 'gems', 'environment'))

# This actually requires the bundled gems
Bundler.require_env

class MyApp < Sinatra::Base
  # stuff

The daemon starts like this:

# This makes sure the bundled gems are in our $LOAD_PATH
require File.expand_path(File.join(File.dirname(__FILE__), 'vendor', 'gems', 'environment'))

# This actually requires the bundled gems
Bundler.require_env

AMQP.start do
  # stuff

However, this loads too many gems. The Sinatra app synchronously publishes its messages using carrot, so it doesn’t need the EventMachine gem or the AMQP gem. Likewise, the daemon doesn’t serve HTTP requests, so it doesn’t need Sinatra, and it’s processing messages asynchronously using EventMachine, so it doesn’t need Carrot.

This isn’t the end of the world for this set of gems; it just makes all the Ruby processes use a little more memory than they otherwise might. However, if the Sinatra app uses one gem that defines Array#foo one way, and the daemon uses another gem that defines Array#foo an entirely different way, then each component must require only its own dependencies.

Fortunately, the bundler makes this really easy: just change the Gemfile to look like this:

gem 'json'

only :app do
  gem 'sinatra'
  source 'http://gems.github.com'
  gem 'famoseagle-carrot', :require_as => 'carrot'
end

only :daemon do
  gem 'eventmachine'
  gem 'amqp'
end

In the Sinatra app, change Bundler.require_env to Bundler.require_env(:app). This loads JSON, Sinatra, and Carrot, but not EventMachine or AMQP.

Similarly, in the daemon, Bundler.require_env becomes Bundler.require_env(:daemon).

But wait, it gets better!

Bundler.require_env can be called more than once with a different environment name each time. Consider tests (which, of course, you wrote first, right?): you want to run them. Now your Gemfile might look like this:

gem 'json'

only :app do
  gem 'sinatra'
  source 'http://gems.github.com'
  gem 'famoseagle-carrot', :require_as => 'carrot'
end

only :daemon do
  gem 'eventmachine'
  gem 'amqp'
end

only :test do
  gem 'rspec'
  gem 'webrat'
end

Then, in your spec, you start like this:

require 'my_app'       # load the Sinatra app so we can test it
Bundler.require_env(:test)    # get rspec and webrat in here

describe "the main page" do
  # stuff

Now each part of your app has its dependencies neatly contained. Each part only gets what it needs, and the different dependencies can’t step on each others’ toes.

Sweet.

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
Popularity: 25% |
Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars
Loading ... Loading ...

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

14 Responses to “Using the Rubygems Bundler for Your App”

  1. Klaus Dieter Klaus Dieter says:

    Looks nice – thanks!

  2. Dan Croak Dan Croak says:

    Very nice. Dependencies are hard. This looks better.

  3. andhapp andhapp says:

    I have been working on a sinatra app with bundler and it has worked like a charm. I did not know about the Bundler.require_env and I was just requiring the environment.rb file bundler creates but this makes it even neater.

    • Sam Merritt Sam Merritt says:

      Yep, it leaves the code looking nice and tidy.

      More importantly, it means you don't have to repeat yourself by saying "gem 'sinatra'", then "require 'sinatra'" later on, so it's impossible for the 'gem' statements and the 'require' statements to get out of sync.

  4. That what I was asking about before few days on StackOverflow!
    http://stackoverflow.com/questions/1706120/sinatr...
    Great work!

  5. Nathan Esquenazi Nathan Esquenazi says:

    Thanks for this writeup which inspired me to integrate this with my sinatra projects. I also ended up baking in bundler support into my sinatra_more gem here: http://github.com/nesquena/sinatra_more . Now when you use the generator for a new application, it will transparently manage all dependencies using this method. Thanks again.

    • Sam Merritt Sam Merritt says:

      I'm glad it was useful.

      sinatra-more looks really interesting. I may have to start using that for its tag helpers; the last view I wrote in this Sinatra app, I was really missing the Merb/Rails-style form helpers.

      • Nathan Esquenazi Nathan Esquenazi says:

        Glad it seems interesting. that is why I wrote sinatra_more for myself in the first place. I really missed the tag helpers, form helpers, named routes and some of the niceties of rails but I loved the lightweight feel of sinatra. Tried to strike a balance as best I could with the gem.

  6. Daniel Daniel says:

    What to do if the gem has a architecture specific stuff in the name like:

    http://gems.rubyforge.org/quick/Marshal.4.8/wxrub...

    • Daniel Daniel says:

      Actually I found that in remote_specification.rb _remote_uri should be:

      def _remote_uri
      "#{@source_uri}/quick/Marshal.4.8/#{full_name}.gemspec.rz"
      end

      instead of what is there. I'll fork and fix this bug if I get some time today.

  7. Empty Empty says:

    Can someone have a look at the layout of this article please? It's hard to understand the different paragraphs… Thanks!

  8. Simon Simon says:

    For future reference.

    I ran into trouble with inline templates ("Errno::ENOENT: No such file or directory – /Library/Ruby/Gems/1.8/gems/bundler-0.9.26/lib/bundler/views/index.haml"). Turned out it's caused by the inline templates I'm using.

    This can be solved by adding "enable :inline_templates" in your Sinatra application.