logo-new mail facebook Dribble Social Icon Linkedin Social Icon Twitter Social Icon Github Social Icon Instagram Social Icon Arrow_element diagonal-decor rectangle-decor search arrow circle-flat
Development

The Tandem Guide to Upgrading Ruby / Ruby on Rails

Alex Spencer Engineering
Dave Lamps Engineering
Steve Schneider Engineering

One of the least exciting parts of being a Ruby on Rails developer is performing language or framework upgrades.

These upgrades do not offer any additional features for the end-users and can take a considerable amount of time — which means they are often deferred for as long as possible. However, they are crucial, as they make your application more secure, more performant, and significantly increase your software’s life expectancy.

In this blog post, we’ll highlight some of the general steps Tandem uses to upgrade Ruby and Rails, as well as best practices for keeping the team moving forward while one or two of developers work on the upgrade.

 

Upgrading Ruby

Upgrading Ruby can be as simple as:

  1. Updating the .ruby-version file
  2. Updating the Ruby version number in the Gemfile
    1. Dockerfile image (if applicable)
    2. Continuous Integration image (if applicable)
  3. Running bundle
  4. Committing the resulting code changes in Git
  5. Sending in a Pull Request

However, many times it can be much more difficult. Here are the more common issues you’ll see and some ways around them:

Dependency Nightmares

If you have too many gems or the gems you do have depend on too many gems, then your project is dependency heavy — which means you’ll likely run into conflicts.

You will know you are in a dependency nightmare when you see errors like this:

activeadmin (~> 1.4.3) was resolved to 1.4.3, which depends on inherited_resources (>= 1.9.0) was resolved to 1.11.0, which depends on actionpack (>= 5.0, < 6.1)

The name of the gem and the version numbers could be anything. But the error still looks like: _____ was resolved to ____ which depends on _______

Solution

The solution to your dependency nightmare is to avoid it altogether. To do this, we recommend consistently following a few rules:

  1. Every gem needs to be pessimistically-versioned to be in your Gemfile. This rule applies across the board. Pull requests changing the Gemfile are rejected unless those changes include pessimistic versions for each gem. In short, the ~> is your friend.
  2. Update early and often. The jump from ruby 2.1 to 3.2 is a tremendously larger leap than the one from 2.7 to 3.2. As soon as a new version of Ruby is released, TAKE THE TIME to update your application and its dependencies. Your future self (and fellow developers!) will thank you.
  3. If you inherit a project that has not been upgraded in a very long time, practice incremental upgrading. Do NOT attempt to jump from version 2.1 to 3.2 directly. First attempt to upgrade to 2.4. Then from 2.4 to 2.7. Then from 2.7 to 3.2. These smaller leaps will surface issues faster, allowing you to resolve them easier and get back on track in much less time.
  4. Bonus tip: it’s typically a good time to inventory which gems you are using/not using. Maybe you can retire some!

 

Upgrading Rails

Upgrading Rails can be significantly more difficult than upgrading Ruby, but the basic steps are:

  1. Write tests and make sure they pass.
  2. Move to the latest patch version after your current version. (see below)
  3. Fix tests and deprecated features.
  4. Move to the latest patch version of the next minor version.

To move between versions:

  1. Change the Rails version number in the Gemfile and run bundle update.
  2. Change the versions for Rails JavaScript packages in package.json and run yarn install, if running on Webpacker. If you are using a different JavaScript package manager, please consult its documentation.
  3. Run the Update task. bin/rails app:update
  4. Run your tests.
  5. Return to step 3 above and fix any broken specs or deprecation warnings.

A few additional tips/tricks can make your upgrades flow much faster/easier too:

 

Tandem Tools & Tricks

  1. Step one of upgrading Rails (above) cannot be emphasized enough: Ensure you have a comprehensive test suite that is passing.
  2. Upgrading incrementally is a crucial rule to implement in your workflow that Tandem can’t stress enough. Trying to jump from Rails 5 to Rails 7 directly is going to result in headaches all around. Even jumping directly from Rails 5 to Rails 6 could cause issues. Upgrade incrementally through the patch versions!
  3. If using a linter such as rubocop, configure it to lint the target Ruby version’s rules ahead of the upgrade and check for any violations. You may need to add overrides for default style preferences that have changed between versions (e.g. Hash syntax, block forwarding).
  4. Be on the lookout for gems that have been dramatically changed or that make large version leaps. They are usually the largest contributors to security issues, performance issues, or dependency conflicts down the road.
  5. The built-in Rails Update task is good, but it’s not 100% comprehensive. To be 100% sure you have changed everything you need, a tool like railsdiff is worth its weight in gold. To use it:
    1. Select your ‘from’ version
    2. Select your target version
    3. Go through each diff for each file and make sure your application mirrors that diff
  6. Use the next_rails gem to ‘dual boot’ your Rails application before diving headfirst into the next version.
  7. Review the official changelog to ensure you are aware of any breaking changes that your test suite may miss.
  8. Consider leveraging deprecation_toolkit by Shopify on a separate branch that you don’t plan to merge. This will integrate with either Minitest or RSpec and assist in identifying areas of your code that require syntax changes to be compatible with your targeted Ruby version. The output of the gem will be saved locally and inform changes to be made on your other branch containing pre-upgrade tasks.
      1. Should you decide to make this gem a permanent fixture in your project, you should also configure your CI environment to retain the /deprecations artifacts

 

Keeping Team Members (and Their Branches) in Sync

Another major pain point in upgrading Ruby or Rails is that changes are impactful to the entire application. If you have a team of 10 developers all working asynchronously, stopping everyone’s work while you upgrade is not ideal. Here is how Tandem recently did a Rails upgrade while allowing the team to continue their work:

  1. Pick a day to merge the upgrade branch. Ideally, early to mid-sprint so there’s plenty of time to revert if necessary.
  2. Notify the team several days before the date that the merge will be taking place. Send another reminder the day before. Use this opportunity to identify any Ruby syntax that will be deprecated as a result of the upgrade. This minimizes the chance of code that’s not forward-compatible being written on other branches concurrently. This can be a frustrating moving target without proper communication. If all contributors haven’t been informed or don’t understand the impact of the upgrade, you may end up with problematic code merged in before you can finish performing the upgrade.
  3. First thing on the appointed day, merge the upgrade branch.
  4. Notify everyone when the merge is complete. This will usually require them to install a new Ruby version on their system once they pull from the main branch (if they had not been using it before on a separate project), so it’s nice to remind your collaborators of this task as well.
  5. The team now merges/rebases off the merged branch and continues their work. Have someone familiar with the upgrade on standby to help with conflicts or other issues. Be prepared to revert the branch if a blocker shows up that isn’t easily solvable.

Upgrading Ruby or Rails may not feel like glamorous work, but it is arguably the single most important task your team takes on. It keeps you and your client’s data more secure. In a best-case scenario, it increases performance efficiency and adds additional developer-friendly features/methods!

It can feel tedious and time-consuming to upgrade, but hopefully following the steps outlined above will make the process much easier on your entire team.

Let’s do something great together

We do our best work in close collaboration with our clients. Let’s find some time for you to chat with a member of our team.

Say Hi