The No-Nonsense Guide to Managing Gem Versions

Update: Derek Prior offers much better advice on how to manage your Gemfile, so you probably want to read his article instead.

If the Ruby code you write never leaves your computer, then this article is not for you. But if you find yourself sharing Ruby code with others, or deploying your Ruby code to a web server, then you have a problem. And that problem is gem versions. Sooner or later, the version of a gem on your computer will not match the version of that gem on your production web server, and your cute little disruptive social media web app will fail in a steaming pile of 500 errors.

Locking Down Your Gem Versions

Fortunately, there is a way to specify exactly which gem versions your app uses by using bundler. List all of your gems and their exact versions in your Gemfile like this:

source :rubygems
gem 'rails', '3.0.3'
gem 'devise', '1.1.5'
gem 'redgreen', '1.2.2'
gem 'capybara', '0.4.0'

Then type bundle install on the command line and bundler will install those versions of the gems as well as create a Gemfile.lock that records the exact versions of all of the gems installed (including dependent gems). Make sure you commit the Gemfile.lock to your repository. That way when you move your code to another computer (your web server, for example) and you type bundle install it will install the gem versions recorded in Gemfile.lock.

So now you have a way of ensuring that your app will use the specified gem versions no matter where it gets installed. Problem solved.

But Wait, There’s More!

Not so fast! Even if you have your gem versions locked down, you may still get an error like this:

$ rake test
rake aborted!
You have already activated rake 0.9.0, but your Gemfile requires rake 0.8.7. Consider using bundle exec.

To understand what’s going on here, let’s take a detour and talk about what happens when you install a gem. At its simplest, installing a Ruby gem simply means fetching the gem’s Ruby files from the Internet and storing them in a specific directory on your computer. (If you’re curious as to which directory, type gem env on the command line.)

If all gems were as simple as this we wouldn’t have a problem. But some gems come with executables. For example, the rails gem comes with a rails executable that lets you create new Rails apps, start a web server, launch the Rails console, etc. The rake gem comes with a rake executable for invoking rake tasks from the command line.

When you type rake test, the directories in your environment’s PATH variable are searched for an executable named rake. In this case, you happen to have the rake gem version 0.9.0 installed and so the 0.9.0 version of the rake executable is executed. If you are running this in a Rails app, the Rails environment will load. When Rails loads, it sets up bundler. And when bundler is setting itself up, it ensures that the exact versions of gems in Gemfile.lock are loaded. If Gemfile.lock says that we must use rake version 0.8.7, bundler throws the error because we have already loaded rake 0.9.0.

Managing Gem Executable Versions

Faced with this problem, we have four alternatives:

  • bundle exec
  • binstubs
  • gemsets
  • flee this postmodern technical dystopia by wandering the Mongolian steppe with a camel and a yurt

While the fourth option is clearly the best, most people prefer one of the blander choices so we will explore those in more detail.

bundle exec ensures that the gem executable matches the gem version specified in Gemfile.lock. So as long as you remember to always prefix your gem executables with bundle exec, the problem is solved:

$ bundle exec rake test

binstubs is an option you can pass to bundler like this:

$ bundle install --binstubs

This installs the gem executables in your application’s bin directory. Then you just have to remember to invoke that executable explicitly:

$ bin/rake test

Gemsets provide you a way to work with separate “sandboxes” of gems. You can build a gemset for your app and only your app will see those gems. That way you can install rake 0.8.7 in your app’s gemset, and still use rake 0.9.0 for other projects. Keep in mind that gemsets don’t solve the problem of ensuring gem compatibility across machines. You would still have to manually check that other computers you deploy your code to are using rake 0.8.7. But gemsets are a good solution to keeping gem executables from interfering with each other. For example, if you install Rails 2.3 in one gemset and Rails 3.1 in another gemset, you will have easy access to two different rails executables that you can use to create new Rails apps, etc. They won’t interfere with each other.

Further Reading

Hopefully this was a gentle introduction into the wonderful world of gem versions. There is a lot more that I didn’t cover that is explained very well in the documentation for bundler. If you want to streamline the process of updating your gem versions, check out my next post, Sanely Updating Your Gems.

You can skip to the end and leave a response. Pinging is currently not allowed.

2 Responses to “The No-Nonsense Guide to Managing Gem Versions”

  1. There is an important distinction to be made between applications and gems. By all means, lock down your application’s gem dependencies to precise versions, but you don’t want to do that with gems you are publishing for other people to use. Those dependencies are transitive and you are imposing a rigid constraint by doing so. For example, if gem a depends on rake “= 0.9.0” and gem b depends on rake “= 0.8.7” you can not use both gems a and b in the same application. A more sane approach is to depend on a range, in this case, and that range depends on the release policy of the gem you are depending on. If it releases breaking changes to patch releases (i.e. 3.2.1), then you have no choice but to lock it to one and only one version. If it only releases breaking changes to minor releases (i.e. 3.2.0), then you can depend on “~> 3.2.0”, which will allow for any 3.2.x release, but not 3.3.0. If they follow Rubygems Rational Versioning, Semantic Versioning, or other, then that means they only release breaking changes in major releases (i.e. 4.0.0), in which case you can depend on “~> 3.2”, which allows for any 3.x.y version (>= 3.2.0), but does not allow 4.0.0.

  2. techiferous says:

    @David Thanks, I agree!

    @everyone If you are a gem author, take David’s advice. Also check out his relevant blog post, rake 0.9 and gem version constraints.

Leave a Reply