Pretty image
Bundler is a gift you should open right away.

You may have heard about Bundler, a rising star in the latest wave of Ruby tools. Bundler’s specialty is libraries management: it helps you to sort out the gems that your projects need.

With all the interesting tools coming out lately, you might be tempted to file Bundler under “cool stuff I’ll eventually look at,” together with the latest testing framework and NoSQL database. However, there is a good reason to adopt Bundler in your own project right now: Bundler is a pay-as-you-go technology. It doesn’t force you through a steep learning curve like other tools do (cough, vim, cough). You can reap a few benefits out of Bundler in a matter of minutes, and the more time you invest in it, the more time-saving features you seem to discover.

In this article I’ll show you three very cheap, very quick ways to get a little help from Bundler. But first, let me explain why you should use Bundler in the first place.

Isn’t RubyGems Enough?

RubyGems is a powerful, flexible package manager. However, there is one place where even RubyGems has trouble reaching: inside your projects.

By “project,” I mean anything from a simple script to a full-fledged Rails app. RubyGems doesn’t know anything about this stuff. When you install a gem, RubyGems just adds it to a global list of system gems. As this list grows, it can become difficult to tell which gems are used by which projects.

You might have experienced some of that confusion yourself. Maybe you checked out the latest version of your team’s project, and saw it crash because of a missing gem. Maybe you were left wondering which gems in your system you could safely uninstall without hurting some long-forgotten project. Or maybe your freshly deployed system crashed because of a gem on the production machine that wasn’t the exact same version you’d been using in development. In general, per-project gems management can turn out to be more trouble than you might expect.

For years, Ruby programmers have rolled out their own custom-made solutions to this problem. Now we have Bundler, which builds upon RubyGems to give you a number of project-focused goodies.

First Benefit: A List of Gems You Can Rely On

First, install Bundler—it’s a gem itself:

 gem install bundler

(You might need to run most commands in this article as a superuser, with the sudo command. Also, you need RubyGems 1.3.6 or later to install Bundler. You can check your current RubyGems version with gem --version, and update it with gem update --system.)

OK, now you have Bundler. Pick one of your existing projects, or just create a test project—a directory containing a simple Ruby script. Go to the project’s root directory, fire up your editor and create a file named Gemfile. In the Gemfile, list the gems that you want to use in your project. Use this syntax:

 source :rubygems
 
 gem "activerecord"
 gem "json"

The first line in the Gemfile points Bundler to a place where it can download missing gems—in this case, the official RubyGems host. If you want to be sure that all the gems in the Gemfile are installed on your system, just issue Bundler’s most popular command:

 bundle install

Bundler checks whether you already have all the gems that your project needs. If you don’t, it installs the missing gems—and the gems they depend upon. When I ran bundle install on my system, that already had json installed, Bundler installed seven more gems: activerecord, and six other gems that activerecord depends upon.

You already got one benefit out of Bundler: now you have an authoritative list of your project’s gems, and you can install them all with one command. You could do the same with a simple script that calls gem install, but Bundler does that in a standard way, so anyone looking at your project will immediately see the Gemfile and understand what’s going on. Also, Bundler looks at the list of gems and their dependencies all at once, rather than one at a time like RubyGems does. As result, Bundler is better than RubyGems alone at preventing conflicts that can result from complex tangles of dependencies. Those conflicts tend to be rare on small project, but they’re more and more likely to happen as your list of gems grows longer.

One word of warning, though: if you install your gems through Bundler, you should also use Bundler to launch your gems’ command-line utilities. For example, instead of writing cucumber, you’d write:

 bundle exec cucumber

If you don’t do that, your commands will probably work anyway, but they may stop working later—for example, when you update your system’s gems.

One More Thing about the Gemfile

If your Gemfile don’t specify a gem’s version, then Bundler will just download the latest version, or use whatever version it finds on your system. However, for some gems you usually want to stick to a specific version, or a restricted range of versions.

For example, maybe you’re using a feature of the json gem that’s been implemented in version 1.4.0—so you need that version, or a later version. But suppose a future version 1.5 turns out to be incompatible with your current code—so you want a version earlier than 1.5.0. You can tell Bundler about these constraints by using the standard RubyGems version operators:

 gem "json", ">= 1.4.0", "< 1.5"

Or specify this range of versions more succinctly with the ~> operator:

 gem "json", "~> 1.4"

Enough with the Gemfile. Let’s look at the lock file, one of Bundler’s killer features.

Second Benefit: No More “It Works On My Machine”

Your Gemfile is your way to tell Bundler: “I want these gems on my system.” However, the Gemfile alone doesn’t solve the problem of running the same project on another system.

Imagine this scenario: in your Gemfile, you specified that you want the json gem, version 1.4.0 or later. On your system, you have json 1.4.3 installed, so Bundler will happily use that version. You colleague Bill, however, has json 1.4.2, which also satisfies Bundler, but happens to be plagued by an obscure bug. The entire project runs fine on your box, but it crashes spectacularly on Bill’s machine.

You might solve this problem by always specifying the exact version that you need for each gem, down to the minor version:

 gem "json", "= 1.4.3"

However, even such a meticulously crafted Gemfile wouldn’t guarantee that you and Bill are sharing the same exact gems, because your project might indirectly depend on gems that aren’t even mentioned in the Gemfile. For example, the activerecord gem depends on the arel gem. If Bill has a different version of arel already installed on his system, you might still have a problem.

To really fix this problem, you have to specify the exact version of each and every gem that your project depends upon, either directly or indirectly, and keep this specification aligned with the gems on your system. Doing that by hand would be a mind-numbing maintenance hassle for most projects. The good news is that Bundler can do it for you—in fact, it already did.

Look in your project’s directory right now, and you should see a file named Gemfile.lock. This file, also known as the lock file, is not meant to be edited by hand. Instead, Bundler silently updates it every time you run bundle install.

Like the Gemfile, the lock file contains a list of gems, although in a different format. While the Gemfile only lists the top-level gems that you’re directly using, the lock file is a complete snapshot of all the gems you’re using and the gems they depend upon, including their exact versions:

 GEM
  remote: http://rubygems.org/
  specs:
  activemodel (3.0.3)
  activesupport (= 3.0.3)
  builder (~> 2.1.2)
  i18n (~> 0.4)
  activerecord (3.0.3)
  activemodel (= 3.0.3)
  activesupport (= 3.0.3)
  arel (~> 2.0.2)
  tzinfo (~> 0.3.23)
  activesupport (3.0.3)
  arel (2.0.4)
  builder (2.1.2)
  i18n (0.4.2)
  json (1.4.6)
  tzinfo (0.3.23)
 
 PLATFORMS
  ruby
 
 DEPENDENCIES
  activerecord
  json (~> 1.4)

You’re never supposed to edit the lock file—Bundler does it for you. When you run bundle install, Bundler checks whether the very detailed information in your lock file is consistent with the sparsely detailed information in the Gemfile. If it’s not, it assumes that you’ve edited the Gemfile, and it updates the lock file to make it consistent with the Gemfile. Then it proceeds to install the exact gem versions that are listed in the lock file, unless they’re already available on your system.

If you add both Gemfile and Gemfile.lock to your repository, your colleague Bill will be able to check out the project, run bundle install, and get the exact same gems that you have, down to the specific version. This alignment goes a long way to avoiding mysterious “it works on my machine” problems.

If Bill wants to add a new gem to the project, he can manually edit the Gemfile and then run bundle install. If he wants to update an existing gem, he can do so with bundle update <gemname>, or just use bundle update to update all gems to their latest versions. Once Bill has committed the updated Gemfile and Gemfile.lock to the repository, anybody on the team can check out the changes and run bundle install to align with the current state of the project.

But wait: what if you check out updated versions of Gemfile and Gemfile.lock, but you fail to call bundle install?

Third Benefit: Make Your Project Bundle-aware

So far, Bundler knows about your project and the gems it needs. On the other hand, your project still doesn’t know about Bundler. Usually this isn’t a problem. By default, Bundler installs your gem in the same global location that RubyGems uses—so you don’t have to do anything special to use the Bundler-installed gems in your project. You can just require the gems as you would with regular, manually installed gems.

If you do that, however, your project will require whatever gems are installed on your system, regardless of what your Gemfile says. If you forget to run bundle install before you run your project, you might expose your code to a gem that is not listed in the Gemfile, or to the wrong version of a listed gem, sliding back into pre-Bundler confusion. Also, as you get into the most advanced features of Bundler, you might decide to install a project’s gems to an alternate location—maybe inside the project itself. Once you do that, your project won’t be able to find the gems in the default system location.

You can prevent all these issues by adding two lines of code to your project:

 require "rubygems"
 require "bundler/setup"

(You need to load rubygems before you require bundler/setup, because Bundler is itself a gem. You can skip the first line if you’re sure that rubygems is enabled by default on all your target systems).

Now your code and Bundler are really working together. You can install gems through Bundler, and Bundler tells your code which gems are available and where to load them from. If you have a gem on your system that’s not listed in your Gemfile, then your code will flat-out refuse to load it. If a gem is listed in your Gemfile but you forgot to install it, your code will fail with a graceful error message that clearly points you to the problem, and you’ll be able to fix the situation by running bundle install. Quite a list of features for two lines of code, isn’t it?

Keep Paying, Keep Going

I showed you three juicy low-hanging fruits that you can pick and taste in a matter of minutes. However, you can do much more than that with Bundler. You can define separate groups of gems for different environments, like separate gems for the development and production machines. You can package your gems inside your project folder and commit them to your repository as part of the project. You can even list a git repository as a source to download gems (for example, by using a GitHub URL), so that you always get the very latest committed version of any library. Bundler’s bag of tricks is surprisingly deep, and most of these tricks require just a few minutes to learn.

I told you it was worth it!

Paolo Perrotta has been developing, writing and speaking at conferences for longer than he’d care to admit. He has worked in domains ranging from embedded to enterprise software, computer games, and web applications. He also wrote the Metaprogramming Ruby book for the Pragmatic Programmers. These days, Paolo works as an itinerant coach for companies adopting agile methodologies. When he’s not on the road, he lives in a small Northern Italian village with his girlfriend and a cat.

Send the author your feedback or discuss the article in the magazine forum.