Red Green Repeat Adventures of a Spec Driven Junkie

How to Upgrade Rails app

tl;dr

  1. Make sure all your tests pass. ((rspec | minitest))
  2. Install Ruby to appropriate version (`(rvm | rbenv) install `)
  3. Update Gemfile version of Ruby: ruby '<appropriate ruby version>'
  4. Update Gemfile version of Rails: gem 'rails', '~> <new version>'
  5. Run: bundler update
  6. Run: bin/rails app:update and make appropriate choices.
  7. Make sure all your tests pass. ((rspec | minitest))
  8. Test on pre-production environments, just in case. ;-)
  9. Deploy to production and test.
  10. On to the next problem!

I want to go over how to update an existing Ruby on Rails application. I will upgrade a Rails application from 5.2.2 to 6.0.3 with details of each step I took showing examples.

You will learn specific steps and what to expect when upgrading a Ruby on Rails application. This article will take you about eight minutes to read.

Saint-Guilhem Cloister source and more information

Introduction

I never had to “upgrade” a Rails app. The documentation sounds easy and you should refer to them. I felt I needed to get specific step-by-step instructions. Hopefully this will help you (or future me) when upgrading a Rails app.

Why??

I had upgrade a Rails project due to security issues from Github’s Dependabot

No time like the present, right?

Requirements

The steps I outline should work for any version of Rails. The most important thing is to have a working Rails application you want to upgrade.

If you want to use the one I have, it is available at: https://github.com/a-leung/tdd_indepth_callbacks/

You can also see all the code changes I made on this branch of the repository:

https://github.com/a-leung/tdd_indepth_callbacks/tree/fix/upgrade_rails_1

Get things in working order

Before upgrading, ensure the application is in a working order. This means:

  • The app runs
  • The automated test suite run and all pass
    • If any, tests don’t pass, solve it.

In my case:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ rspec
....

Finished in 0.04772 seconds (files took 0.7398 seconds to load)
4 examples, 0 failures

This is a small app, larger apps benefit from automated tests when upgrading Rails because without tests, one won’t know the side effects up upgrading the app’s foundation until the worst possible time(s).

Install and Configure Ruby version

Each version of Rails requires a minimum version of Ruby. The documentation lists required Ruby versions for each version of Rails.

If the version of Ruby installed is equal or greater than the version required by Rails, move on to the next section. Otherwise, follow the next steps to install a new version of Ruby.

Install Ruby

To install Ruby 2.6.0, I used rvm to install it:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ rvm install 2.6.0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18.1M  100 18.1M    0     0  3243k      0  0:00:05  0:00:05 --:--:-- 3394k
No checksum for downloaded archive, recording checksum in user configuration.
ruby-2.6.0 - #validate archive
ruby-2.6.0 - #extract
ruby-2.6.0 - #validate binary
ruby-2.6.0 - #setup
ruby-2.6.0 - #gemset created /home/vagrant/.rvm/gems/ruby-2.6.0@global
ruby-2.6.0 - #importing gemset /home/vagrant/.rvm/gemsets/global.gems..................................
ruby-2.6.0 - #generating global wrappers.......
ruby-2.6.0 - #gemset created /home/vagrant/.rvm/gems/ruby-2.6.0
ruby-2.6.0 - #importing gemsetfile /home/vagrant/.rvm/gemsets/default.gems evaluated to empty gem list
ruby-2.6.0 - #generating default wrappers.......

Painless!

Configure Gemfile for new version

If you try to run or update your application now, this error may appear:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bundle update
Your Ruby version is 2.6.0, but your Gemfile specified 2.4.5

This requires an update of the Ruby version in the Gemfile.

 source 'https://rubygems.org'
 git_source(:github) { |repo| "https://github.com/#{repo}.git" }

-ruby '2.4.5'
+ruby '2.6.0'

Tricky, not too bad.

Upgrade Rails

When you started the project, do you remember installing Rails? In my previous articles, it was via:

$ gem install rails
...
$ rails new <application name>

Are the same steps taken?

Nope! This isn’t like other software where you just download a new version.

Update Rails’ entry in the Gemfile

Upgrading Rails is nothing like installing Rails. To upgrade Rails, on an existing Rails project, update the rails version in the Gemfile:

 # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
-gem 'rails', '~> 5.2.2'
+gem 'rails', '~> 6.0.2'

Easier than installing, right?!

Actually updating

The part where the actual updating of Rails happens is when running: bundle update

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bundle update
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Using rake 13.0.1 (was 12.3.2)
Using concurrent-ruby 1.1.6 (was 1.1.4)
...
Using uglifier 4.2.0 (was 4.1.20)
Using web-console 4.0.3 (was 3.7.0)
Bundle updated!

If bundle returned an error, especially about compatibility, follow the next steps, otherwise, you’re good to move onto the next section to run bin/rails app:update.

Compatibility Errors

Larger and longer lived projects will run into cases where not all gems are compatible with the version of Ruby or Rails. The error may look like:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bundle update
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundler could not find compatible versions for gem "actionpack":
  In snapshot (Gemfile.lock):
    actionpack (= 5.2.2)

  In Gemfile:
    rails (~> 6.0.2) was resolved to 6.0.3.rc1, which depends on
      actionpack (= 6.0.3.rc1)

    sass-rails (~> 5.0) was resolved to 5.0.7, which depends on
      railties (>= 4.0.0, < 6) was resolved to 5.2.2, which depends on
        actionpack (= 5.2.2)

The solution would be to also update the associated gem’s version to the one compatible with Ruby and/or Rails.

Another option would be to remove the gem from the Gemfile until completing the Rails update process.

This may be the most complicated part of the process, especially if gems are incompatible or not updated.

If you have a lot of trouble, feel free to contact me.

Run: bin/rails app:update

This is the final step in upgrading Rails: getting latest configurations for the application. Just run: bin/rails app:update and follow the prompts.

Warning, there can be breaking changes to your application if you accept all changes suggested. (i.e. getting a new clean routes.rb file.)

Be careful with your choices!

This is what the process can look like:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bin/rails app:update
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
  >> Coffee::Rails::TemplateHandler.call(template)
To:
  >> Coffee::Rails::TemplateHandler.call(template, source)
 (called from <main> at /home/vagrant/tdd_indepth_callbacks/Rakefile:6)
   identical  config/boot.rb
       exist  config
    conflict  config/routes.rb
Overwrite /home/vagrant/tdd_indepth_callbacks/config/routes.rb? (enter "h" for help) [Ynaqdhm] a
...

THOR_MERGE !?

If you want to merge changes, configure the THOR_MERGE environment variable with an appropriate editor before running bin/rails app:update.

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ THOR_MERGE=/usr/bin/vim  bin/rails app:update

That way, you can incorporate changes into your file as merge instead of copying and pasting.

Test

Now, the moment of truth: did the rails update work?!

This is where tests come into play: use them! They will check if everything still behaves the way you expect it and tell you if Rails made a change that causes your app to misbehave.

I would advise performing the following:

  • Run automated tests
  • Deploy and test in pre-production environments
  • Deploy and test in production environments

Past you may have hated writing all of those tests, present you definitely appreciates them! ;-)

Conclusion

Upgrading Rails is a smooth process when taking the proper steps:

  1. Making sure your application is in working order.
  2. Upgrade to appropriate version of Ruby.
  3. Upgrade Rails, associated gems, and configurations.
  4. Test and deploy.

Optional: Update Bundler

While you’re updating Ruby AND Rails, why not make sure you have the newest version of Bundler, the tool that glues everything together?!

The project’s Gemfile was still using Bundler 1.17.2.

You can find out which version of bundler your app is using by looking at the bottom of the Gemfile.lock file:

BUNDLED WITH
   1.17.2

Update Bundler

Upgrading Bundler is like installing it the first time: gem install bundler. To specify a specific version, use the following format:

$ gem install bundler:<version>

For example, installing Bundler 2.1.4

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ gem install bundler:2.1.4
Fetching bundler-2.1.4.gem
Successfully installed bundler-2.1.4
Parsing documentation for bundler-2.1.4
Installing ri documentation for bundler-2.1.4
Done installing documentation for bundler after 5 seconds
1 gem installed

With a new version of Bundler, have your Rails project use it by running:

bundle update --bundler

For my project, the result:

vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bundle update --bundler
...
Using uglifier 4.1.20
Using web-console 3.7.0
Warning: the lockfile is being updated to Bundler 2, after which you will be unable to return to Bundler 1.
Bundle updated!
vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ git diff
diff --git a/Gemfile.lock b/Gemfile.lock
index be7a8b4..76e6106 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -239,4 +239,4 @@ RUBY VERSION
    ruby 2.4.5p335

 BUNDLED WITH
-   1.17.2
+   2.1.4
vagrant@ubuntu-xenial:~/tdd_indepth_callbacks$ bundle --version
Bundler version 2.1.4

Now your project is using another version of Bundler too.