What are the best-practices for upgrading gems to newer versions? What
sort of tips and techniques can save time and headaches?
I built this guide based on my real-world experiences over years of gem
migrations, including a recent upgrade to Rails 4.1, RSpec 3.0, and
Twitter Bootstrap 3.2. There are some more specific examples of errors
you might encounter at this article on the Rails on Maui blog: Specific
Issues Upgrading Gems to Rails 4.1, RSpec 3, and Twitter
Here's my favorite reasons for keeping gems relatively current:
- If you work on several projects, keeping the gems and ruby version
consistent makes your coding more productive as you don't have to
keep adjusting for which version is which. Web searches tend to find
relatively recent versions first. It's relatively annoying to be yak
shaving issues that turn out to be "oh, that doesn't work in that
older version of Rails".
- Recent versions of gems will have fixes for bugs and security
issues, in addition to new features. With popular open source
projects, new bugs are quickly discovered and fixed.
- Updates are much easier if you stay relatively current. I.e., it's
much easier to update from Rails 4.0 to Rails 4.1 than to go from
Rails 3.0 to Rails 4.1.
That being said, recent versions can have new bugs, so it's best to
avoid versions that are unreleased or that haven't aged at least a few
Some Gems Will Be Way More Difficult to Update
Large libraries, like Rails, RSpec, Twitter Bootstrap, etc. are going to
take more elbow grease to update. Typically if a major version number is
updating, like Rails 3.x to 4.x and RSpec 2.x to 3.x, that's going to
require lots of code changes. Semantic versioning also comes into play.
Going from Rails 3.x to Rails 4.x is more difficult than Rails 4.0 to
Rails 4.1. There's a similar story with RSpec 2.x to 2.99, compared to
going to RSpec 3.0.
Techniques for Smoother Gem Upgrades
Locking Gem Versions
Unless you have a good reason, don't lock a gem to a specific version as
that makes updating gems more difficult. In general, consider only
locking the major Rails gems, such as rails, RSpec, and bootstrap-sass,
as these are the ones that will likely have more involved upgrades.
Don't Upgrade Major Libraries Too Soon
3 Reasons to wait a bit before gem updates:
- Dependencies among gem libraries are not yet resolved. I had
tried upgrading to RSpec 3 and Rails 4.1 a couple months ago, but it
was apparent that I had to fix to many other gems to get them to
3. Thus, I retreated back to RSpec 2.99 for a while. Now, as of
August, 2014, the gem ecosystem was ripe to move to RSpec 3.0. So
unless you have a good reason, it's best to wait maybe a couple of
months after major upgrades are released before migrating.
- Bugs may be lurking in changed code. If you wait a bit, the
early adopters will find the bugs, saving you time and frustration.
The more popular a gem, the faster it will be put to rigorous use.
- *Security*/ problems may have been introduced. This is pretty much
a special case of bugs, except that this a possibility of a
malicious security change. If you wait a bit, hopefully somebody
else will discover the issue first.
Don't Use Guard, Zeus, Spring, Spork, Etc. When Upgrading
Tools that speed up Rails like Zeus and Spring are awesome productivity
enhancers, except when upgrading gems. I found that they sometimes
correctly reloaded new versions of gems. That means massive frustration
when they are not picking up the gems you actually have specified. The
corollary to this is to run your tests using plain
rspec rather than
the recommended ways for speeding up testing, such as the
It's not necessary to introduce the added complexity of the test
accelerators when doing major library updates. Once you've updated your
gems, then try out your favorite techniques for speeding up running
tests. I've learned the hard way on this one. The
scripts below are awesome for ensuring that pre-loaders are NOT
for x in spring rails phantomjs zeus; do
pgrep -fl $x;
for x in spring rails phantomjs zeus; do
pkill -fl $x;
There are a lot of discussions about the value or lack of for an
emphasis on Test-Driven Development (TDD). However, one thing that's
indisputable is that having a large library of tests is absolutely
helpful for upgrading your gems.
Naturally, it's an iterative process to get tests passing when updating
gems. First, make sure your tests suite is passing.
You can try updating the gems one by one until you get a test failure.
Then the issue becomes one of figuring out which related gems you might
want to update to fix the test failure.
If you don't have good tests coverage, a great place to start is with
integration tests that do the basics of your app. At least you'll be
able to quickly verify a good chunk of your app can at least navigate
the "happy path" as you iterate updating your gems.
Alternate Big or Baby Steps
If you've updated gems recently, sometimes you can run
and everything works great. Recently, that strategy failed miserably
when I tried going from Rails 4.0 with RSpec 2.2 to Rails 4.1 and RSpec
3. An eariler attempt shortly after the releases of Rails 4.1 and RSpec
3 clearly showed that many dependent gems would have to get updated. A
few months later, I still had many issues with trying to update too much
When this happens, take small steps and kept tests passing. I.e., don't
bundle update without specifying which gems to update. You might
update 60 gems at once! And then when tests fail, you won't be able to
easily decipher which dependency is the problem. Specify which gems to
update by running the command:
bundle update gem1 gem2 etc
Then after updating a few gems, run
rspec and verify your tests pass.
Then commit your changes. Consider putting a summary of how many
tests pass and how long it takes. The length of time is useful in case
some change greatly increases test run time. Or if you notice run time
or the number of tests dramatically decrease. Plus, this ensures you ran
the test before committing!
On a related note, you can see which gems are outdated with this
Try bundle update
Remember I told you not to do a
bundle update? Once you're getting
closer to finishing your gem updates, all big gems are updated, and all
tests are passing, and deprecation warnings are addressed, then it's
time to run
bundle update and then run
rspec to see if your tests
pass. If you don't have adequate tests, then be ready to do some
adequate manual testing. Even if you have lots of tests, you still need
to do manual testing if you upgrade a UI library such as
sass-bootstrap. Besides testings, check the bundler output or the diff
Gemfile.lock to see what got updated.
Troubleshooting Gem Upgrades
Read Error Messages Carefully and then search Google and Github Issues
Too often Ruby developers will blindly copy-paste their error messages
into a Google search without really reading the console output
carefully. This can actually waste more time, since thinking about the
problem for a moment can often give you a solution without Google, or
you'll write a better search query. If you don't find what you need on
Google and you have an idea what gem is causing issues, the next place
to search is the issues page for the gem's Github repository.
Remember to do these 2 types of searches rather than spending too much
time inserting print statements or launching the debugger! If you don't
get any search hits, then typically you have some problem in app
customizations (see below).
Visit the Gem Repository on Github
Some essential places to look at when upgrading gems are:
- README.md file (shown on the main page of repository). Some projects
might have a NEWS.md or CHANGELOG.md file.
- The Github issues list for a gem (and search here)
- The Github commit history for a gem, sometimes switching branches.
Errors or deprecation messages can come from compatibility issues among
your gems. The RSpec 3 upgrade had many such issues. If you're having an
upgrade issue, then a concise, detailed post of a new issue typically
results in a very quick response.
Try an RC Version on RubyGems
Sometimes the fix you need has already been released to RubyGems in an
RC version (RC means Release Candidate).
bundle update <gem> seems to
not pick RC versions. You have to specify these manually. I search for
gems on RubyGems so often that I created a Chrome search shortcut.
Here's an example of an RC version gem that I'm currently using:
gem 'simple_form', '>= 3.1.0.rc2'
Try a Github Gem Version Rather Than a RubyGems Version
Sometimes what you need has not been shared with RubyGems, yet the issue
has received commits on Github. In that case, you can use the Github
version of a gem. This might be on a specific branch of a gem, or even
another user's fork of a gem.
For example, this image shows that the last version bump of the
gem is behind several commits.
If you needed those commits post gem release, here's an example of the
syntax to specify the very-latest version of a gem (the tip of the
gem 'gon', github: "gazay/gon", branch: "master"
Sometimes what you need is something less than the most current version,
or a specific branch, or a fork of the gem.
Consider Forking a Gem
Sometimes you need to fork a gem for some changes. If you've never done
this, it's a very worthwhile thing to try out, and it's easy! For
example, if you had wanted to update to rspec 3 sooner than later and
didn't want to see tons of deprecation messages, then your only option
was to fork the gems that had the deprecated syntax. Once you've
verified the validity of your changes, consider submitting a pull
request. Here's an example of a fork and commit of the
zeus-paralleltests gem that loosened a gem
You should typically prefer a rubygems version of a gem rather than a
github version. Thus, after some months, you should try to remove any
previously necessary github references in your Gemfile.
Order of Gems in your Gemfile Can Matter
I ran into a case where including rspec-instafail before rspec resulted
in zeus failing due to
rspec-instafail failing to recognize that I was
using rspec 3. Simply placing
rspec-instafail after loading
the Gemfile fixed that issue.
I had a clue that was the issue due to this stack dump. Note how the
bundler is loading rspec-instafail, and when I looked at the source
code, I could see why file
rspec_2.rb was being loaded (2nd line of
the below stack dump)
zeus test ✹ ✚ ✭ [15:37:26]
/Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/rspec-core-3.0.3/lib/rspec/core/formatters/progress_formatter.rb:1:in `<top (required)>': uninitialized constant RSpec::Support (NameError)
from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/rspec-instafail-0.2.5/lib/rspec/instafail/rspec_2.rb:1:in `<top (required)>'
from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/rspec-instafail-0.2.5/lib/rspec/instafail.rb:11:in `<module:RSpec>'
from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/rspec-instafail-0.2.5/lib/rspec/instafail.rb:1:in `<top (required)>'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler/runtime.rb:85:in `require'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler/runtime.rb:85:in `rescue in block in require'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler/runtime.rb:68:in `block in require'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler/runtime.rb:61:in `each'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler/runtime.rb:61:in `require'
from /Users/justin/.rvm/gems/ruby-2.1.2@global/gems/bundler-1.6.2/lib/bundler.rb:132:in `require'
from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/zeus-0.13.3/lib/zeus/rails.rb:162:in `test_environment'
from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/zeus-0.13.3/lib/zeus.rb:166:in `run_action'
from -e:1:in `<main>'
In general, when doing relatively major gem upgrades, you really need to
evaluate customizations to these places. Typically, deprecation messages
will tell you which customizations to remove or alter. Sometimes, you've
monkey patched some gem to work around some issue, and this would be the
place where you'd do that (and forget that you did it!).
- Any initializers in the
config/initializers directory. Review each
- Any customizations in your environment files in the
config/environments directory, such as
- Any customizations for running specs: a.
Each file in the
Example of Next Steps when Upgrading a Gem
Here's an example of where updating related gems help.
bundle update capybara fixed the following error
Capybara::RSpecMatchers::HaveText implements a legacy RSpec matcher
protocol. For the current protocol you should expose the failure messages
via the `failure_message` and `failure_message_when_negated` methods.
The final error I got was this one, from
`failure_message_for_should_not` is deprecated. Use `failure_message_when_negated` instead. Called from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/cancan-1.6.10/lib/cancan/matchers.rb:11:in `block in <top (required)>'.
`failure_message_for_should` is deprecated. Use `failure_message` instead. Called from /Users/justin/.rvm/gems/ruby-2.1.2@bpos/gems/cancan-1.6.10/lib/cancan/matchers.rb:7:in `block in <top (required)>'.
A quick google search reveals that
cancancan fixes the issue:
Once I got all tests passing, I tried to update to Rails 4.1, but ran
into this issue:
bundle update rails ✹ ✭ [20:31:38]
Fetching source index from https://rubygems.org/
Bundler could not find compatible versions for gem "activemodel":
simple_form (>= 0) ruby depends on
activemodel (< 4.1, >= 4.0.0) ruby
rails (~> 4.1) ruby depends on
I verify I'm on the current maximum GA version of simpleform,
but I find that there's an RC version, so I specify that in the gemfile.
It's important to note that "bundle update" will tend not to pull in RC
versions of gems, which you sometimes need after major libraries are
gem 'rails', '~> 4.1'
gem 'simple_form', '>= 3.1.0.rc2'
> bundle update rails simple_form
Using rails 4.1.4 (was 4.0.8)
Installing simple_form 3.1.0.rc2 (was 3.0.1)
Your bundle is updated!
After the 4.1 upgrade, I addressed a number of deprecation warnings.
DEPRECATION WARNING: Implicit join references were removed with Rails 4.1.Make sure to remove this configuration because it does nothing. (called from block in tsort_each at /Users/justin/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/tsort.rb:226)
config.active_record.disable_implicit_join_references = true
Then I got this warning with a full stack dump.
Warning: you should require 'minitest/autorun' instead.
Warning: or add 'gem "minitest"' before 'require "minitest/autorun"'
The stack dump was useless, but the search for error message on Google
indicating that the issue had something to do with
check of my gem version revealed that my gem version was not current.
> bundle update shoulda-matchers
Installing shoulda-matchers 2.6.2 (was 2.5.0)
And that fixed that issue!
Thanks to Mike Perham, Ed Roman, Ben Ward, and Greg Lazarev for
reviewing drafts of this article.
Please let me know if this article helped you or if I missed anything!