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.
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
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.