A Guide to Update Gems with bundle update

Tips and tricks to master bundle update by following incremental, controlled and safe steps

Gonzalo Rodriguez
Jul 19, 2018 · 6 min read

tl;dr— Synopsis: Updating Gems cheat sheet.

“selective focus photography of faceted red gemstone” by Joshua Fuller on Unsplash

Have you ever been part of a massive Rails app upgrade, with tons of gems not being updated for years?

Was that closer to a joyful experience or a nightmare?

Have you ever felt that you — or the team — could do better at keeping up-to-date with Gemfile‘s gem updates? Was that in part because of an unexisting process around it?

If some of this rings true to you, I think you’ll find what follows helpful.


Step 1 — Fix security vulnerabilities

To identify them you can use RubySec’s bundler-audit utility:

$ gem install bundler-audit$ bundle audit --update

Say — for an example Rails app — it outputs:

Name: sprockets
Version: 3.7.1
Advisory: CVE-2018-3760
Criticality: Unknown
URL: https://groups.google.com/forum/#!topic/ruby-security-ann/2S9Pwz2i16k
Title: Path Traversal in Sprockets
Solution: upgrade to < 3.0.0, >= 2.12.5, < 4.0.0, >= 3.7.2, >= 4.0.0.beta8
Vulnerabilities found!

If you get “Vulnerabilities found”, you need to find the smallest version update — patch-level only, if possible — that fixes the vulnerability so that feature regressions are less likely.

From the example above, you can infer that the smallest possible version change that makes sprockets secure again is moving from 3.7.1 to 3.7.2 — given that one of the listed solutions is >= 3.7.2 and the current vulnerable version is 3.7.1.

In this case, you may use:

$ bundle update --patch --conservative <vulnerable-gem-name>

to attempt a patch-level only (--patch) update to the Gemfile.lock with as minimal effect on other gems as possible (--conservative).

Why not just update everything?

You’ll make a potential vulnerability in your app go away quickly.

What if you mix more updates in one big pull request and it gets temporarily blocked? Maybe a regression in one of the app main user flows — which is being caused by a different gem update — slows down the whole security update. Would you want that?


Updating gems

Why not bulk updating?

How much more “regression risk” do you have when updating rails compared to a “development” gem likeweb-console?

Probably updating rails version is much more risky for your app and your end-users.

Split updates

So, what’s the split?

A simple one can be “Non-production”–“Production” gems.¹ Less and more risky respectively.


Step 2 — Update “Non-production” gems

$ bundle update --conservative --group test development

Bundler’s --conservative option prevents updates in any “Production” gem that is also a dependency of a“Non-production” gem.

Note: Make sure you’re using bundler v1.16.4 or higher. There was a known issue when mixing --group and --conservative options in previous versions.

Play safe

  • run automated test suite — i.e. $ bundle exec rake test or similar;
  • test manually — as much as needed.

Commit

$ git commit -am "Update non-production gems"

Step 3 — Update “Production” gems

Else — a more cautious approach — would be to split bundle update in 3 separate sub-steps, with increasing risk levels.

$ bundle update --patch --strict$ git commit -am "Update gems to latest patch version"
$ bundle update --minor --strict
$ git commit -am "Update gems to latest minor version"
$ bundle update --major
$ git commit -am "Update gems to latest major version"

You may even decide to run only a subset of the above.

It’s up to you if you want to make separate pull requests for these or not. At least, you should have separate commits for each grouped update.

In case of a feature regression caused only by one of those commits, you could revert — or discard — it in isolation, instead of putting yourself in an “all-or-nothing” situation.

You’ll have a better chance of making progress

Again, be safe

  • test manually — as much as needed;
  • Check each updated gem’s CHANGELOG — if available — for potential backward-incompatible changes.

Step 4— What about Gemfile version constraints?

gem 'rails', '~> 5.1.6'

and 5.2.0 is already out?

Well, in that case none of the previous commands updated rails version for you.

Of course, that’s expected… and probably why you — or someone else — intentionally set a constraint for rails in the Gemfile in the first place.

How many gems are constrained from reaching new versions?

$ bundle outdated --groupsFetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Outdated gems included in the bundle:
===== Without group =====
* actioncable (newest 5.2.0, installed 5.1.6)
* actionmailer (newest 5.2.0, installed 5.1.6)
* actionpack (newest 5.2.0, installed 5.1.6)
* actionview (newest 5.2.0, installed 5.1.6)
* activejob (newest 5.2.0, installed 5.1.6)
* activemodel (newest 5.2.0, installed 5.1.6)
* activerecord (newest 5.2.0, installed 5.1.6)
* activesupport (newest 5.2.0, installed 5.1.6)
* arel (newest 9.0.0, installed 8.0.0)
* railties (newest 5.2.0, installed 5.1.6)
* websocket-driver (newest 0.7.0, installed 0.6.5)
===== Group default =====
* pg (newest 1.0.0, installed 0.21.0, requested ~> 0.18)
* rails (newest 5.2.0, installed 5.1.6, requested ~> 5.1.6)

So pg (postgresql adapter) is also constrained (to the 0.x series while 1.0.0 is already out).

This last step of the process is the riskiest.

Now what?

  1. Changing version constraint string in Gemfile entry
  2. $ bundle install
  3. Regression verification; including running automated test suite, manual testing and checking gem’s CHANGELOG for backward-incompatible changes
  4. $ git commit -am "Update <gem-name> to <new-version>"

Splitting is your friend here, again.

Someone from the team—maybe even you— using git bisect in the future, trying to find the cause of a feature regression, will be very thankful to notice that the offending commit is updating only one gem instead of many at the same time.

If you prefer to execute this 4th step as separate — more focused — tasks, you can create tickets/user-stories in your project/app backlog, so you — or someone from the team — can pick it up at a later time.


Conclusion

Go to your app and try execute “Step 1”:

$ bundle audit --update

Any vulnerable gem needs an update? Yes? Update gem and “New pull request”. Win!

Then try to update “Non-production” gems with

$ bundle update --conservative --group test development

Do that — say, once a week — for a couple of weeks.

Build momentum…

After a couple of weeks you’ll be confident; your update process is running smooth. Team members are starting to like it; you can now slowly start adding the rest of the steps…

Remember, you’ve reduced risk by:

  • splitting updates in smaller chunks; and
  • running this process as frequently as possible.

Recap


[1] You may call them “Development”–“The rest” as well.

Cedarcode

Adding value from day one. Effective software development & expertise for your products and teams.

Gonzalo Rodriguez

Written by

Co-founder & Full-Stack Developer @ cedarcode - It's not about doing as much as you can, it's about doing more of what's important, meaningful and impactful.

Cedarcode

Cedarcode

Adding value from day one. Effective software development & expertise for your products and teams.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade