Today I want to talk about my experience upgrading my work’s largest application to Rails 5 from Rails 4.2. I started on December 29th, 2016, and we successfully deployed in January of 2018. (Just a hair over a year :))
Disclaimer: I work at ProctorU, the thoughts here are my own and not my employer.
Admittedly, Kickstarter wrote a really great writeup on their upgrade, I wish I would have had when I started the upgrade. By the time I saw it we were already locked and loaded for our January deployment.
First I’d like to write about the plan I put together, because every large project always begins with a plan. My first initial issue started on December 29th, 2016. In this issue, I had written a number of various notes throughout my process of getting the app working locally, things like X and Y gems needed to be updated to even run.
Included in the issue was basically every single gem we had in our application at the time as a checklist. I would list the gems, then list the current version, and then available upgrade to it. This in hindsight turned out to be almost a waste of time, because by the time I got to the bottom of the list for writing the available upgrade, a new version would most likely be out.
If you’re not already using Dependabot to help manage your dependencies, you should be. We added this to our workflow, and let me tell you… it’s really really nice to have for gem updates.
TLDR of this section: I made a list of changes I could find from Rails 4.2 -> Rails 5, and wrote them down along with gems that needed to be updated for compatibility. Having started the upgrade process a good amount of time after Rails 5 was released, most of the gems we use were updated.
Biggest things we had to watch out for
Keyword argument changes for the controller/integration tests was by far the biggest hassle in the update. It was so bad, in order for us to get the app merged, we had to turn off deprecation messages for a bit. We have a large number of tests, so going through each test and fixing it in the same PR as the upgrade to Rails 5 quickly wasn’t an option.
Migrations are now versioned with the version of Rails they were created in. This threw so many deprecation messages because we have a large amount of migrations (460 total!) that we run during our CircleCI test runs.
Some of our controllers were not respecting strong_params properly, so we ran into some unexpected issues we had to resolve through testing. Or weird things would happen where Rails would in 4.2 return true but in Rails 5 it would be 'true' or vice-versa.
So there’s a few interesting notes I have from the plan and process of upgrading our app.
- When we started updating gems, by having them listed out, we noticed we could drop a decent amount of gems from our stack. Which was a) less to update and b) better for the app since we weren’t really using them.
- I attempted to upgrade us to use ActiveJob during the process, before I ran into so many merge conflicts I wanted to go buy a farm.
- We later determined we’d tackle the ActiveJob upgrade after Rails 5 was running.
- Simultaneously, another one of my teammates attempted an update from Turbolinks 2 to Turbolinks 5. Despite our best efforts, we weren’t able to get that in production before hand and it’s on our list for things to update to.
- Sometimes, gems need new maintainers, a few of our gems needed updates for compatibility. If you’re using a gem, make sure you can either a) maintain it or b) rewrite and go without it if the author drops support.
- Long running branches are terrible. I cannot even begin to tell you how many times I had the branch ‘green’ with all tests passing and then I’d have to merge in master and things would break from changes upstream. If you can, make smaller PRs, get the app running on the new rails version, and then go back through and do the rest.
After working 11+ months, I had been pretty ‘salty’ the upgrade wasn’t out and it kept getting broken, so we came up with our weekly testing hour. We implemented a weekly Tuesday testing hour, where we’d all hop on a test server and run through the app anyway we could think of to solve bugs.
There was a few minor issues we’d find in these hour sessions that we were able to resolve pretty quickly, once we had a few sessions where there was one or no bugs, I felt super confident that we would be able to have a successful deployment. One of the biggest concerns left was the dreaded ‘rollback’ scenario.
To test this, I literally deployed Rails 5 to our internal team staging environment, then deployed Rails 4.2 on top of it. We ran into a show-stopping bug because during the upgrade, I had attempted to switch our Cookie Serializer. BigBinary, which was a great resource for new Rails 5 features, had a post about it here: http://blog.bigbinary.com/2014/12/23/migrating-existing-session-cookies-while-upgrading-to-rails-4-1-and-above.html
Turns out, when the app was upgraded from Rails 4.1 -> 4.2 we didn’t change that. So when I attempted to change it to hybird, users who’d login during the time a Rails 5 branch had been deployed would have had their cookies upgraded, so when we reverted they’d get 500 errors and have to force clear cookies (#NotGood).
Once we identified the serializer, we removed it from the pull-request and tested the flows for reverting again. After that, we were ready to ship it. I wasn’t totally alone during this upgrade process, I had help from many of my teammates through moral support, code reviews, ALL of the testing we did and last but not least, those who contributed parts of their time to help with backporting things to Rails 4.2.
On January 22nd, 2018 we deployed Rails 5 and had no issues reported from the upgrade within the first 24 hours. We did get one issue that was a major oversight on my part. Sometimes we’d lose emails if our process rebooted before the email sent, and we narrowed it down to we needed to set our ActiveJob adapter to inline, which was the default before Rails 5.
All in all, getting to Rails 5 was a major accomplishment I’m proud of our team for reaching and I’m super excited with all the things it allows us to use now (like ActionCable!).
Thanks for reading!