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'
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
- 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.
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.