How I Upgraded Rails to Version 5.1.5 And Lived To Tell The Taleby@eti
1,584 reads
1,584 reads

How I Upgraded Rails to Version 5.1.5 And Lived To Tell The Tale

by Eti Dahan NokedNovember 17th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

There are many posts and instructions out there describing how the upgrade process should be done. I chose to write this post to describe my upgrade process and emphasise the issues I had to tackle along the way. The issues are not written in the official rails documentation, but hidden in rails’ source code, other gems, stack-overflow questions or other sources. Upgrading the gems was done in 3 chunks, with each including up to 10 gems. After testing locally, the upgrade was tested for a while in a staging environment.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - How I Upgraded Rails to Version 5.1.5 And Lived To Tell The Tale
Eti Dahan Noked HackerNoon profile picture

Our Rails’ version was 4.2 and we needed to upgrade to a later version mostly because Heroku stack requires it.

There are many posts and instructions out there, describing how the upgrade process should be done. There is, of course, the official rails documentation (which is great!), and a lot of other great posts (which can be found in the resources section below).

You’re probably wondering… why do we need another post?

Well, as much as I searched, there was no single post that describes how to actually perform the upgrade, step by step, with all the issues you might run into along the way. I chose to write this post to describe my upgrade process and mostly emphasise the issues I had to tackle along the way, which are not written in the official manual, but hidden in rails’ source code, other gems, stack-overflow questions or other sources.

This how you shouldn’t do it

Starting from the beginning, and how naive I was. Upgrading rails? Sure, no problem. At first, I just changed the rails version in my Gemfile

gem ‘rails’, ‘5.1.5

and then simply ran this command to install the dependencies specified in my Gemfile:

>> bundle install

Oh wait, I forgot to actually update the rails’ version

>> bundle update rails

At that point, I got a million (no, not really a million) errors indicating my gems were incompatible with the new Rails version. I should have seen it coming, right? But there are so many of them.

Let’s try to update them all to the latest available version:

>> bundle unpdate

Easy, right?

Great, now it’s testing time. Trying to run the server…

>> rails server

Boom. Nothing works.

The right way

At that point, I had so many changes in my code (created when I tried to fix some of the errors manually) and so many gems were updated (most of them unnecessarily) that I had no idea where to continue my investigation. I decided to do the smart thing and do the whole process again, but wisely this time.

Step #1: Upgrading the Gems

I chose to take the time and divide the work into small chunks of gem upgrades. I used ‘is ready to rails 5?’ to understand which gems had to be upgraded. For the gems that were not provided there, I used rubygems to investigate their dependencies and tried to understand whether they could be used in rails 5 or not.

Some of the gems are already built-in rails 5 (such as ‘rails-api’, ‘test_after_commit’), so you might be aware of those as well.

Upgrading the gems was done in 3 chunks, with each including up to 10 gems. After testing locally, the upgrade was tested for a while in a staging environment, and only then was merged to production. The intent was to minimise the risks.

At this point you must be wondering what about the other gems and how do I know if an upgrade is needed? To be honest, I decided to let future Eti deal with that.

Step #2: Inherit from ApplicationRecord instead of ActiveRecord::Base

Starting from Rails 5.0, all models inherit from ApplicationRecord which is a new superclass for all app models, similar to app controllers subclassing. This gives apps a single spot to configure app-wide model behaviour.

Practically, that means that I had to create an ApplicationRecord class and make all models inherit from it. Pretty straightforward.

class ApplicationRecord < ActiveRecord::Base
    self.abstract_class = true

Step #3: Tag migration with its created Rails version

ActiveRecord migrations now need to be tagged with the Rails version they were created under. One of the reasons for this is that strings in Rails 4.2 have a default size of 4 bytes. In Rails 5.0, their default size is now of 8 bytes.

In my case, all the migrations were created with Rails 4.2, so I added this version to all of them.

class BuildBasicSchema < ActiveRecord::Migration[4.2]

Step #4: Be aware that ActionController::Parameters no longer inherits from HashWithIndifferentAccess

Calling params at your controllers will no longer return a hash, but an ActionController::Parameter object:

> params
=> <ActionController::Parameters {“id”=>”123", “controller”=>”users”, “action”=>”create”} permitted: false>

This change should not affect accessing the keys in the params hash like params[:some_key], but you should pay attention when using any one of these methods: any?mapsymbolize_keysdig, or any other method that depends on being able to read the hash regardless of permitted? and remember to permit the parameters first and then convert it to a hash.

params.permit([:params, :to, :permit]).to_h

Step #5: Change commands’ directory

At this point, I decided to try my luck, and run rails server.

Disappointed, I had to solve this error that popped up:

config/boot.rb:6:in `require’: cannot load such file — rails/commands/server (LoadError)

After googling a bit, I discovered that the rails’ authors changed the commands directory structure, so instead of finding the wanted command at rails/command/server it was now located at rails/command/server/server_command.

In ‘config/boot.rb’, I required these directories:

require ‘rails/commands/server/server_command’

You can also check the rails repository and the new command structure here.

Step #6: Remove some deprecated or deleted methods

remove raise_in_transactional_callbacks since it is no longer supported. The following was deleted from the application’s config file.

# Do not swallow errors in after_commit/after_rollback callbacks.
  • remove quotes around Rack::Cors in the application’s config file. You can read more about Rack::Cors here.
  • before_filter has been deprecated in Rails 5.0 and removed in 5.1. Use before_action instead.
  • Attribute_changed? is deprecated. You can read about replacements for change related methods here.
  • Replace ‘render text‘ with ‘render plain

Step #7: Define the callback before usingskip_before/after/around_action

skip_before_actionskip_after_action, and skip_around_action will now raise an ArgumentError if the specified callback doesn’t exist.

If you wish to keep the current behaviour, you can prevent these errors from showing with ‘raise: false’ and that lets you opt out of the new behaviour.

skip_before_action :verify_authenticity_token, raise: false

That’s it. The server ran successfully. Now what?

I used postman to test our endpoints to make sure I got the wanted responses and it seemed like the application looked pretty much ok.

Moving forward, I started to run our specs beginning with the controllers. I followed one rule — the fixes should be in the code and not in the tests, except for the next step:

Step #8: Fix the Controllers’ specs

HTTP request methods will accept only the following keyword arguments: paramsheadersenvxhrformat. So I had to add params to all my tests

Step #9: Make sure not to pass ActiveRecord to .find method

If you do so, you’ll probably encounter the following error:

You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`

So, as the message indicates, try to pass the object’s ID instead of its instance.

Below is an example from my tests:

Step #10: Make sure to specify a response in all controllers’ actions

In Rails 5, controller action responds with ‘No content’ (204) by default, unless a response is specified. This great post explains the subject.

And that’s it.. It’s done. Our Rails 5.1.5 app is up and running!

More resources:

Know some other steps I missed? Share with me in the comments section.