Finishing an Upgrade

Finishing an Upgrade

When working on upgrades, we use the Dual-Boot technique opens a new window so we can gradually update the app to work with the current version and the next one.

Eventually, the upgrade is done and the app is ready to drop the old version, but how do we handle that process? What are the steps between one upgrade and the next?

Removing the Old Version From the Codebase

Most of this guide is useful when updating any dependency, not just Rails, since you can use the dual-boot technique to run 2 versions of Ruby or 2 versions of any gem.

Usually, the first step we take is to remove any code related to the previous version: conditionals in the Gemfile, conditionals in the codebase, temporary monkey-patches and back-ports, and any other code that might not be needed anymore.

Once the old version is not referenced anymore, we can replace the Gemfile.lock file (which includes the old version) with the Gemfile.next.lock file.

After this, running bundle install or any other command should run the application in the new version.

All applications are different, so we also have to check other places depending on what we are updating. For example, if we finished a Ruby upgrade then we might need to also update CI configuration files, docker files, documentation, setup instructions, and any other place referencing the old Ruby.

Note that, most of the time, fully removing the dual-boot setup can be a lot of work if other tools were already configured with this in mind and it will be reintroduced soon for the next version jump. There’s no need to remove the Gemfile.next files. We can ignore any issue caused by misconfigurations that happen for the next version until we start working on the next upgrade.

Version-specific Post Upgrade Tasks

Before creating the PR that removes all the code from the previous version, we may need to do some extra work that couldn’t be done while dual-booting. Let’s look at a few examples to make this more clear.

Rails 5.0 Introduced the Rails Version in Migration Class

When we upgrade from 4.2 to 5.0 opens a new window , some defaults for migration methods change between versions. Therefor Rails introduced the concept of adding the Rails version in the ActiveRecord::Migration class.

This feature is not supported in Rails 4.2 so the change cannot be made while we are still dual-booting. For older migrations to work properly in Rails 5.0 we need to add the [4.2] suffix to all migrations.

We have to make sure migrations are always run with Rails 4.2 during the dual-boot phase, and then, when removing it, we also update all the migrations. So, after this, Rails 5.0 will apply them properly.

Rails 7.0 Introduced the Rails Version in the Schema Class

A similar issue happens during 6.1 to 7.0 upgrades: in this case the change happens in the defaults of the schema file. Rails introduced a similar concept but adding the Rails version as a suffix of the ActiveRecord::Schema class.

We can’t apply this change while we are still dual-booting since Rails 6.1 wouldn’t know how to process the schema. We can include this change after we drop that older version.

Depending on your app and its dependencies, you may need to do different tasks right after removing the dual boot, these are just 2 examples.

QA and Deploy

Now that everything was removed, it’s a good time to do a final round of tests and QA. There might be steps in the deployment process or the CI setup that were never executed using the new version.

Now is a good time to make sure everything is compatible with the current version before going to production.

After the QA process is done and the current state of the app is approved, it can be deployed to production and monitored.

New Framework Defaults

When upgrading Rails, one important change that happens after every version jump is that the framework defines new defaults. These defaults are configuration values that can be adopted over time if desired.

They don’t have to be changed just because they are the new defaults, an application can continue using the previous configuration.

To help with the decision and transition to new defaults, Rails generates a file called new_framework_defaultsX-Y.rb in the initializers.

Now, once the upgrade is done and we are running the app in the new version, we can start reviewing the different configurations and decide which ones we want to adhere to and which ones we don’t.

The important thing here is to not do these changes while we are still dual-booting since most of them are not compatible with the older version of Rails and will only add complexity to the upgrade process.

Note that you should also NOT update the load_defaults statement while dual-booting, since that will make the application use all the new defaults all at once. It will introduce more points of possible failure since the app’s behavior can change drastically.

Fix Deprecation Warnings

The new version is in place, the new defaults were reviewed and the app is configured. The next step is to start fixing deprecation warnings, preparing the code for the next upgrade.

The Last Rails Upgrade

When we are working on a Rails upgrade and we get the app running in the latest stable release, there’s an optional step we can do to keep the app always ready for the next version: the app can be dual-booted between the current stable version and the main branch from Rails’ GitHub repository. opens a new window

By doing this, the setup is ready to quickly run the app with an unreleased version of the framework. We can find any issue and fix it even before it gets into a stable state.

It’s important to note that the main branch from GitHub may include changes that are not final (changes could be reverted for example). There’s no need to keep it compatible constantly.

Conclusion

There’s a process we follow to do upgrades, and there’s also a process to finish them. It is important to not miss the cleanup steps to leave the codebase in a good state, ready for the next jump.

Do you need help with an upgrade? We can help! opens a new window

Get the book