[Update: added example of updating the sextant gem, which causes Rails to be updated as well.]
Hey Ruby developers,
When you run bundle update to update your gems, it updates all of them at once. If your app stops working or your tests start failing, it can be pretty hard to figure out which gem update broke it.
There are a couple of solutions I’ve seen people use to solve this. Neither of them is that great:
- Lock the versions numbers in your Gemfile. But hey, that’s a pain and it’s what Gemfile.lock is for. Locking the version numbers in the Gemfile should be the exception, not the rule.
- Run bundle update gemname.
You might think bundle update gemname would just update that gem. But no, it also updates the gem’s dependencies—whether they have to be updated or not. In fact, updating a third party gem can even upgrade you to a new version of Rails behind your back.
Here’s what the Bundler doc says about bundle update gemname:
UPDATING A LIST OF GEMS
Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the Gemfile.lock.
For instance, in the scenario above, imagine that nokogiri releases version 1.4.4, and you want to update it without updating Rails and all of its dependencies. To do this, run bundle update nokogiri.
Bundler will update nokogiri and any of its dependencies, but leave alone Rails and its dependencies.
Yeah, sure, that sounds nice. But read that last sentence again: “Bundler will update nokogiri and any of its dependencies.” Well, heck. Some gems depend on a ton of other gems. And why not? Nothing wrong with that. And it makes sense to update the dependencies if something new is required for the update to work. But Bundler updates them no matter what. And now you’re back to the problem I stated at the beginning: even if you intend to update only a single gem, you still end up updating a whole boatload of gems. So when your app breaks or your tests fail, it takes a lot of time to figure out why.
Want an example of an unexpected side-effect of bundle update? I have a good one. Let’s say you’ve installed the sextant gem into your Rails app so you can see your Rails routes in development mode by navigating to /rails/routes. (It saves time compared to rake routes since the environment is already loaded.) In this example you are on Rails 3.2.2 and sextant 0.1.2. Now you run bundle update sextant to update to 0.1.3. Do you know what you just did? You upgraded Rails from 3.2.2 to 3.2.6. I don’t know about you, but I don’t like having my version of Rails updated just because I got the latest version of some little helpful gem.
When writing this blog post I tried a few scenarios. I found something else interesting:
With bundle update gemname, even if there is no newer version of that gem, it will still update everything the gem depends on.
Here’s an example. My app has version 0.3.4 of haml-rails, which at the moment is the newest version. I run:
bundle update haml-rails
After that, git diff informs me that my Gemfile.lock now has newer versions of the journey, json, multi_json, and sprockets gems. Even though it didn’t find an update to haml-rails.
The Solution: bundle update ––source gemname
Bundler has a solution, but in my opinion it’s hard to understand the documentation.
Here’s what you do:
bundle update ––source gemname
I started diving into Bundler’s code and specs to see exactly what this does but it was taking more time than I wanted to spend. But I’ve been using this for more than a year and it works great. I recommend this be your default way to update a gem. If it doesn’t work due to a dependency conflict with other gems then you can always fall back on bundle update gemname.
As far as I can tell, using ––source is the equivalent of the following, but without all the work and headache:
- Specifying version numbers for everything in your Gemfile.
- When you want to update a gem, running gem list -r gemname to find out its latest version number.
- Changing the version number in your Gemfile for just that one gem.
- Running bundle install.
The name of a :git or :path source used in the Gemfile(5). For instance, with a :git source of http://github.com/rails/rails.git, you would call bundle update ––source rails
By the way, the bundle update documentation does mention conservatively updating dependencies. Here is what it has to say: “For more information, see the CONSERVATIVE UPDATING section of bundle install(1) bundle-install.1.html.” The bundle install section on conservative updating does indeed describe how to update dependencies conservatively. However, this section assumes you are specifying version numbers in your Gemfile. As I mentioned above, I prefer to do that only as an exception.