Bundler added new CLI options in the last two releases that allow you to influence how bundle update decides which versions to update to.

The new options give you a lot more control than before when updating gems but are also a bit complicated to understand. Let’s have a look:

The Goal: Be more conservative

The main reason behind all the new options is to allow a more “conservative” approach of updating gems. “Conservative” is quite a broad term, but for Bundler it means two things:

  • Let’s not jump too many versions at once
  • Let’s not update more gems than I asked for

This can be useful in certain scenarios, for example when updating a gem because of a security vulnerability. Or when you’re behind a few versions on a gem and want to update it in smaller steps.

Let’s not jump too many versions at once

By default Bundler chooses the most recent version if you tell it to update a single gem or all gems. So for

bundle update json

it would look for the most recent release of the json gem and if your dependency graphs allows it, it would update to that. There are two cases where that’s maybe not what you want:

  • Security vulnerabilities: You need to update a gem as quickly as possible to fix a known security problem. You’re using an older version of that gem and rather than jumping several versions forward you prefer to just update to the latest patch release that fixes the problem. For example you would update the json gem from version 1.8.3 to 1.8.4 instead of going straight to 2.0.3.

  • Deeply integrated gems: Some gems are very heavily used in your codebase and every breaking change in the library will cause a lot of work. You haven’t updated this gem for a while (because it’s a lot of work, duh), but now you want to or have to. But let’s not get too crazy and jump several major versions at once.

Starting with Bundler 1.13 you can now specify what versions Bundler should prefer when running bundle update. These new options reference semantic versioning with its definition of version numbers:

The new command line options are:

  • --patch prefer updating only to next patch version.
  • --minor prefer updating only to next minor version.
  • --major prefer updating to next major version (current default).

“Prefer” means that no available versions are removed from consideration, to help ensure a suitable dependency graph can be created. This does mean some gems may be upgraded to unexpected versions. If you’re really sure you don’t want that, you can additionally specify a --strict option which will actually remove versions from consideration. Of course that makes it a lot harder satisfy the dependency graph and in some cases could even raise a VersionConflict.

Have a look at the docs for the new options for more detail and examples. You can also use them with bundle outdated to see what gems are outdated given your prefered update strategy.

These new options are a port of the existing bundler-patch plugin, which allows you to use all of this in older versions of Bundler and goes a bit further in helping you with checking and updating gems with security vulnerabilities.

Let’s not update more gems than I asked for

By default, when you update a gem using bundle update, Bundler will update all dependencies of that gem, including those that are also dependencies of another gem. Bundler calls these “shared” or “overlapping” dependencies.

A very common example for this is rack, which a lot of gems depend on. Let’s take a look at this minimal Gemfile:

source "https://rubygems.org"

gem "thin"
gem "rack-protection"

And assume we have a Gemfile.lock with thin on an older version which we want to update now. Since Bundler updates all dependencies of thin, it will also update rack in this case:

$ bundle update thin

Fetching gem metadata from https://rubygems.org/...............
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Using daemons 1.2.4
Using eventmachine 1.2.3
Using rack 2.0.1 (was 1.6.5)
Using bundler 1.14.6
Using rack-protection 1.5.3
Using thin 1.7.0 (was 1.6.4)
Bundle updated!

Which might be surprising to you since you asked it to update thin, not rack and both top-level gems would be satisfied with the older version of rack (so it doesn’t need to update rack to satisfy the dependency graph).

Bundler 1.14 introduced a new flag that allows you to not update any shared dependencies, it’s called --conservative. The same thin update looks like this now:

$ bundle update --conservative thin

Fetching gem metadata from https://rubygems.org/...............
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Using daemons 1.2.4
Using eventmachine 1.2.3
Using rack 1.6.5
Using bundler 1.14.6
Using rack-protection 1.5.3
Using thin 1.7.0 (was 1.6.4)
Bundle updated!

As you can see it only updated thin and left rack alone. That means there is no risk of it unexpectedly breaking any of your other dependencies relying on rack.

Summary

These new command line options give you a lot more control in how you can update your gems. The --conservative flag mimics behavior that was possible before by editing the Gemfile and bundle install, but it’s a lot clearer and explicit now.

It still has to be seen in what circumstances these strategies really make sense. It’s always great to have more options for specific workflows, but they also complicate an already quite complicated dependency resolution algorithm.