Share
Explore

icon picker
Sample Ruby on Rails Upgrade Plan

Here's a sample plan that was provided for a recent, successful Rails upgrade.

Current Status

We start these proposals by capturing a snapshot of the current state of the system.

Containers

The application is deployed on the Heroku-16 stack, which reached end-of-life on May 1st, 2021. It's no longer possible to deploy code changes to applications running on this stack. Getting to a supported stack should be the highest priority. This requires upgrading to Ruby 2.5.9.

Dependencies

The application depends on a large number of stale dependencies, some of which have been abandoned and do not support newer versions of Rails. We'd use the metric, which is a measure of how up to date your dependencies are. It essentially sums [date of most recent release - date when the version you're using came out] for each of the libraries your app uses. Your application has a libyear of 496. Anything over 100 is extremely concerning.

Rails Version

Rails 5.0 hasn't received support or security updates since April 2018. Upgrading Rails versions below 5.2 is significantly more difficult than recent upgrades.

Test Coverage

About half of the controllers and 2/3 of the models have no test coverage. While we'll do manual testing of a few specific critical flows that you provide testing instructions for, we can't guarantee that untested code paths will upgraded properly.

Monkey Patching

lib/ext contains a bunch of extensions to core Ruby classes. This metaprogramming makes upgrading riskier.

OS (Development)

The dockerfile runs on debian jessie, which was released in 2015. It will receive security updates until 2015, but it's not a good base to be developing in. For example, there are no official ruby 3 docker images targeting debian jessie.

Upgrade Roadmap

The goes over major changes. The explains new major features. We'll go through every item in the upgrade guide and check the application.
The right way to upgrade Rails is to go minor version by minor version and to make as many small backwards compatible PRs as possible to reduce risk. Rails releases a new minor or major version each year. You're currently on 5.0, so to get to the latest Rails we need to upgrade to 5.1, 5.2, 6.0, 6.1, and finally 7.0. We'll follow the following series of steps for each version.

Upgrade Ruby to Latest Compatible Version

We'll start each Rails version upgrade by first upgrading Ruby to the compatible version. For Rails 5.0 that's Ruby 2.4.

Dual Booting

"Dual boot" is the ability to switch between different versions of your application dependencies without changing branches. The big benefit is that you can test the same code under both versions. This allows us to make individual small PRs against master that are tested against the next version of Rails (so we know they work) and also pass against the current version (so they can be merged immediately). This reduces the need for long running branches and big diffs.
that I'd encourage anyone working on the app to read.

Technical Details

We dual boot via a shell script which swaps between two different "Gemfile.lock" files built off of a single Gemfile. You write something like this in your Gemfile:
if next?
gem 'rails', '~> 6.0'
else
gem 'rails', '~> 5.2'
end

Then you use a shell script which we add to the repo:
$ rspec # current behavior
$ script/next.sh rspec # new behavior

Your Gemfile.lock is untouched, which reduces the chance of messing something up in production. We'll introduce a new Gemfile.next.lock which tracks the dependency versions for the upgraded app.
There are other ways to dual boot (like Shopify's bootboot bundler plugin), but our recommendation is the above.

Deprecation Warnings

We'll address all deprecation warnings thrown by the application. There are a number of deprecation warnings thrown when running the test suite. Once we're able to deploy to Heroku we'll also hook in to the runtime deprecation logging to log deprecations in production that might occur in untested codepaths. This is implemented through the Rails notification subsystem.

Upgrade Dependencies

Next we get the current version of Rails running with dependencies upgraded such that bundle update rails targeting the next version succeeds.
The following gems need to be upgraded to get to 5.1 (~15 more need to be upgraded on the way to 7.0):
devise
jbuilder
simple_form
The following gems are no longer maintained and need to be migrated away from:
paperclip (should likely migrate to kt-paperclip)
test_after_commit (replace with built-in Rails functionality)

Test and Deploy

Pass Test Suite under Both Versions

The safest upgrade path is to make small backwards compatible fixes until we can pass the test suite under both versions simultaneously. Then the upgrade deployment is as simple as bumping the rails version in the Gemfile.

Review Application Manually

Along the way we'll keep a log of changes we make to application code. We'll then review the entire codebase to try to find untested code paths that hit the same breakage.

Deploy the Upgrade

Do the upgrade! Assuming we succeed in our series of small PRs the only diff in this deployment will be versions in the Gemfile/.lock and new framework defaults.

Remove Compatibility Code

Sometimes it's impossible to support two versions of Rails with the exact same code. There might be code like this sprinkled through the app:
if Rails::VERSION::MAJOR == 6
new behavior
else
old behavior
end

We'll remove this code once the upgrade is successful.

Review New Framework Defaults

Each version of Rails changes some of the default configs and adds new ones. The initial upgrade will retain the former version's defaults for safety.
Once we're on the new version we'll consider each of the new defaults and take the good ones.

Repeat until Rails 7.0

Redo all these steps to move to 5.2, 6.0, 6.1, and 7.0.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.