Keeping your Rails apps up-to-date with the latest versions of Rails and Ruby is not an easy task at the best of times, and it can quite regularly get pushed to the bottom of the pile of ‘things that need to get done’. When we finally got the go-ahead to start the upgrade for one of our longest running and biggest client projects (an equity release mortgage provider) we knew we had our work cut out for us.
In total there were seven applications and two gems that needed to be upgraded, and all were in varying states of age and complexity. The oldest application used Rails 3.2 and Ruby 1.9.3, and was the biggest of all of them. With this in mind, we took a deep breath and got to work.
I wanted to share a few of the things that we learnt along the way and, if we had known at the start, we would have done differently.
Get your test coverage up as high as possible
Writing tests is so important to us at Kyan so having a good test suite on the apps was a huge benefit. Having said that, there was still some work to do to increase the coverage, especially on the older ones. I can’t stress how important well written tests are to helping you with an upgrade.
Check your syntax with Rubocop
I like code consistency. A lot. Rubocop does a great job of keeping us all on the same page when it comes to the style and syntax of our Ruby code. It’s useful for upgrading too, as by specifying the version of Ruby that you’re upgrading to, it can pick up issues that you may not have expected.
A word of warning if you use the autocorrect option though... it can sometimes have undesirable results. We had quite a few occasions where it made a complete mess of the code. If you are going to use it, make sure you run your specs afterwards. A better option may be to use a tool that will display the offences inline in your code like ALE in Vim or ruby-rubocop by misogi in VS Code.
Identify those out of date gems
Gems get old and fall out of favour, so check that you don’t have any gems that are no longer being supported. If any do pop up, then there is usually an alternative, but be prepared for a bit of refactoring. We had quite a heavy reliance on state_machine and a large part of the upgrade was spent refactoring that old code to work with AASM.
Don’t disappear down the rabbit hole
If your app has been around for a while then you will inevitably find code that you just know could be rewritten better, faster, more concisely and without Rubocop chastising you for the method lengths. You might look at it and think, “I can improve that in no time”, but before you know it you’re travelling deeper and deeper into the code as the knock-on effects of your small improvement spiral out of control. Don’t do it! Make a note of it in your favourite issue tracking software and leave it for another day.
Docker is your friend
We use Docker, and it’s an invaluable tool when upgrading your Rails apps. Especially when you want to quickly switch Ruby versions. I’m not sure I could live without Docker now.
Upgrade to the most sensible Rails version first
Kind of an obvious this one, but if your Rails apps are a few major versions behind the current Rails, upgrading in stages will make things a little easier, even if the temptation to jump the whole of Rails 4.x is strong to try and shortcut the upgrade. Luckily we only had one app that was at Rails 3.2 and all the others were a mixture of 4.0 and 4.1.
Rebase regularly
If you have development continuing in your production environment while the upgrade is happening make sure whenever a new feature or bug fix goes live that you rebase your master branch into your upgrade branches. This one came to bite us a bit at the end of the upgrade. When we came to rebase, there were a lot of issues to resolve due to commits into master going back months. If we had been rebasing regularly it would have saved a lot of time in regression testing. Speaking of which...
Regression test everything
Another obvious one, but if you don’t regression test everything, then you’re bound to introduce bugs into your production environment. On this project we had great clients that understand the need for proper testing before signing off the upgrade. Our own internal testing team made sure the applications where in a good enough state to pass over to the client, which meant that we could be sure we were delivering something that was stable.
Of course, now that the upgrade is finished, a newer version of Rails has been released, so we need to to upgrade again! Could we have upgraded to edge at the start? Possibly, but we weren’t sure how long our upgrade would take and didn’t want to risk being in the position of running the applications on a non-production ready codebase. As it happens, the new upgrade should be a lot simpler anyway (5.1.2 -> 5.2), especially considering all the work that we have put in to improving test coverage and code quality.
Nine months later we are finished and deployed. All the sites are running as expected, and the client is happy. The objective now is to make sure upgrades are kept high on the priority list and we don’t let any applications get out of date.
Easy, right?