2 tips to debug problems when upgrading from Rails 4 to Rails 5

4 minute read

Recently we upgraded our application from Rails4 to Rails5. Most of the process went smoothly. Compared to the transition from Rails2 to 3 or even 3 to 4, this one is a much easier transition. The official upgrade guide helped a lot. During the process we learned a few things that might be useful to you. A way to upgrade stress-free and 2 tips to debug problems resulting after the upgrade.

Tip 0: Bundle the commits

A lot of files had to be changed and few new files added to support this rails upgrade. Instead of putting it all in a single commit and pushing to production, we extracted out Rails4 compatible Rails5 changes all into a group of commits. Eg: strong-parameters related changes were all in a single commit, changing factory-girl to factory-bot in a commit and so on. This helped alleviate a lot of stress. We deployed these to production with ease. It also helped other team members to ease into the newer version of the app.

Once these prior commits went live, we deployed the actual commit that upgraded the app to Rails5. Things went smooth for a few days until we started noticing couple of small problems. The following 2 tips helped identify and solve them.

Tip 1: Check the middlewares

One of the background workers stopped working. It seemed to have to do with activerecord connection objects to the database. But we didn’t know what exactly to fix. The internet-suggested fixes looked like band-aids that might cause some other problems in the future.

That’s when we got the idea to check the middleware stack of our application, both after the upgrade and before. The plan was to check if there are any differences in the items. If anything new was added and old items removed. If so, we’ll have a more specific direction to look into in search of the fix.

The idea paid off.

Running rails middleware in our Rails5 version of the app showed this:

use SecureHeaders::Middleware
use Raven::Rack
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use RequestStore::Middleware
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
use Rack::Keymaster::DecoderPlugin
use OmniAuth::Strategies::SAML
run MyApp::Application.routes

Running rake middleware in the Rails4 version of the app showed this list:

use SecureHeaders::Middleware
use Raven::Rack
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00007fca49f59400>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use RequestStore::Middleware
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::Keymaster::DecoderPlugin
use OmniAuth::Strategies::SAML
run MyApp::Application.routes

Among other differences, the relevant one to our current database related problem is this: Rails4 had ActiveRecord::ConnectionAdapters::ConnectionManagement, but Rails5 did not.

Googling this, we found out why this was removed and what we can do to fix the problems caused by it from this StackOverflow answer.

When the problem lies deep in the bowels of the Rails code, it may be hard to reason about the exception stacktraces. One simple way is to check these middlewares as a big part of the Rails version upgrade will be in the middlewares.

Tip 2: Compare the http request and response objects… in the Browser

Here’s the problem. Our app has home-grown authentication system. We use Okta to sign-in the user. There’s a nice feature where the app redirects to a specific page on successful sign in. (Devise has it too.) The upgrade broke this. It now redirected only to the root page after signing in from Okta. That’s because the url stored in the session cookie wasn’t accessible. And that’s because the session cookie that had the url wasn’t sent to the server by the Browser.

I tried the previous approach of searching through the middleware stack. But didn’t find any reason as to why the cookie didn’t come through.

It occured to me that I could run both versions of the app (rails5 and rails4) in different ports and use to developer tools to check for any differences between the 2 apps.

For this, I monitored the problem-causing request: The POST request coming to our app from Okta.

The first difference I noticed was that the Rails5 app added a new header: Referrer-Policy. So I looked the internet if this could cause the problem. It didn’t.

The second difference I noted was that the cookies were slightly different in Rails5. The app’s cookies had an extra attribute called Strict=Lax. This wasn’t there in the Rails4 app’s cookies. Readinp up on it lead to the excellent blog from Makandra. Apparently cross-origin non-GET requests don’t take the app’s cookies to the server. That’s why the session cookie didn’t come to our app during the Okta redirected POST request.

All I had to do to fix this issue is to revert to the previous setting for setting the “SameSite” attribute in all of the cookies, Like this:

MyApp::Application.config.session_store :cookie_store, :key => '_my_app_session', :same_site => :none

This overrides the new way (SameSite:Lax) thereby allowing the cookies to be sent.