Last month I worked on upgrading GOV.UK’s biggest application
(in terms of code, users and volume of content)
from Rails 3 to Rails 4. Here’s the monster pull request.
I wish I could say it was a straightforward undertaking. In the end, it involved
one Rails patch, patches to three other open source libraries, and a lot of sleuthing
There’s a lot you can do to reduce the risk and the pain of the transition from Rails 3 to 4.
The TL;DR? Instead of one big-bang upgrade to Rails 4, try and split the process
into lots of mini-upgrades. Here’s what I recommend:
1. Understand the major changes
The Guide for Upgrading Ruby on Rails
is a good place to start. It provides a good overview of the major changes
2. Backport as much as you can to the current version of your app
A lot of the changes you’ll make for the Rails 4 upgrade will be
backwards-compatible with the Rails 3 version of your app, making it possible to
backport a lot of it. This will make the final upgrade to Rails 4 smaller,
simpler and safer. Once you have a working Rails 4 branch, go through commits
and see which ones can be cherry-picked back onto the Rails 3 version. This is
where having well-formed atomic commits will make your life much easier.
3. Move to strong-parameters
Rails 4 deprecates both
mass-assignment protection. To help ease the transition to Rails 4, I recommend
moving to the strong-parameters
gem on the Rails 3 version of your app before performing the Rails 4 upgrade. In
my experience, the move to strong-parameters can be a tricky one and doing it
separately will make dealing with any issues much easier.
4. Audit your Gemfile
Upgrading from Rails 3 to 4 will almost certainly require some major updates to
your Gemfile as many libraries tend to break compatibility between major versions.
As above, you can save yourself a lot of headache by updating as many gems as you
can on the Rails 3 version of your app before the switch to Rails 4.
In our case, we went a step further and performed a complete gem
audit, looking at all the outdated
gems to figure out:
- Is there a newer version of the gem that is compatible with both Rails 3 and 4?
- How big are the changes between the current and newer version?
- How much risk is associated with updating the gem?
Armed with this data, we were able to update, test and roll out a
updates on the existing Rails 3 code in a planned and controlled manner. This gave
us a chance to catch and deal with any compatibility issues early and reduce the
number of risky gem updates in the final upgrade to Rails 4.
5. Don’t jump straight to the latest version of Rails
Going from Rails 3.2 to Rails 4.0 will be an order of magnitude easier than
jumping straight to 4.2, or whatever the latest version is – you’ll get the
benefit of a bunch of deprecation warnings, and thus fewer breaking changes. The
subsequent upgrades from 4.0 to 4.1 and then to 4.2 should be simpler and less
risky as a result. That said, once you’ve made it to Rails 4.0, don’t delay in
getting to Rails 4.1 or later as 4.0 is now end-of-life
and will no longer be receiving security updates!
6. Good test coverage helps a lot, but poorly-formed tests are a pain
It’s times like this when a comprehensive test suite is super helpful. Having said
that, some tests end up causing as much pain as they save. I’ve found this
particularly the case with tests that make excessive and unnecessary use of
stubs and mocks. A significant number of the commits in this upgrade were solely
rewriting tests that had broken, not because behaviour had changed, but
because the existing tests were not robust enough and were often tightly coupled
to the internal implementation of the methods under test. Lesson here: avoid
7. Start at the lowest level and reduce the noise first
My focus was to get the unit tests passing first. Although this was the bulk of
the work, it helped to make the whole process a little less overwhelming, and
once the unit tests were green, getting the rest of the test suite to pass was
much easier. Getting rid of the deprecation warnings as an early step also
helped to build some momentum and reduce the amount of noise in the test output.
8. Avoid non-reversible migrations and session changes
Even with thorough testing, QA and preparation, there is always a small risk
that you will need to roll back your Rails 4 code. Make this as easy as possible
for yourself by avoiding changes that cannot be easily rolled back. The main
things to avoid are:
The latter is particularly important, as user sessions that have been upgraded to
the new signing code cannot be reversed. This means that if you have to roll
back to Rails 3, you’ll break a bunch of user sessions. I would recommend not
setting the new
secret_key_base config attribute with the upgrade and living
with the deprecation warnings until you are confident that your Rails 4 code is
stable and won’t be rolled back. You can then upgrade to the newer session
handling code as a separate piece of work.