RailsConf, which took place in Portland, Oregon in May, is arguably the biggest and best Ruby on Rails conference in the world. It is the best place to go for newcomers and first-time conference goers, as the sessions tend to be geared towards more general approaches and strategies. By comparison, the Ruby Conference, which took place in Denver last November, has a more technical deep dive into the language and frameworks. This year, RailsConf 2022 focused on tips and tricks for upgrading Rails, and introduced a new upgrade strategy to elevate your Rails experience.
Rails Upgrade Trips and Tricks
I would like to first cover some general tips and tricks you should be using for any upgrade strategy. A lot of these you’ll probably already know or have figured out by yourself, but it’s good to brush up on them.
1. Start with Deprecation Warnings.
The first thing you should do is start with the deprecation warnings. I highly recommend handling these prior to the upgrade, because once you upgrade, these warnings usually turn into errors.
2. Keep Changes Minimum.
Only do the minimum needed during your upgrade. When you start the upgrade process, it can be very tempting to slip in that one small bug fix you found during the upgrade, but it’s a trap! Upgrading Rails is such a huge change, you shouldn’t add any more to it. Better to log another ticket and fix it later.
3. Small Increments.
It’s important to make sure you are upgrading in small increments. Remember- it’s safe to upgrade to the latest patch version at any time with minimal effort and changes. If you are upgrading to an older version of Rails that has later patch versions, it’s beneficial to skip “.0” patches. Patches are not just bug fixes, but also security updates as well. If you deploy a “.0” patch version to production, you’re leaving yourself vulnerable.
For example, when upgrading:
Don’t do this:
Do this instead:
1. 5.0.3-> 220.127.116.11
2. 18.104.22.168 -> 5.1.7
It’s safest to go from minor version to minor version one upgrade at a time. Each minor upgrade to Rails comes with new features and changes. Therefore, the more minor upgrades you skip, the more changes you’ll have to deal with.
In this example, if you’re on Rails 5.0 and you want to upgrade to Rails 5.2, you may be tempted to upgrade directly to it. Don’t do that. Instead, upgrade to the latest patched version of your current minor version, then upgrade to the next minor patch version. It’s slower but easier, safer, and more manageable.
4. Take Advantage of Tooling.
There are lots of tools around that can help facilitate this upgrade process:
- Run rails app:update. The built-in update command is available and walks you through upgrading.
- Rails Guides. The Rails docs are some of the best and most comprehensive in the industry. They have a whole section dedicated to upgrades and what to watch out for between each version.
- Railsdiff.org. Every update brings changes to the default app that’s created when you run Rails new. Those changes matter for Rails default configuration, and also how feature implements change. Railsdiff.org is a fantastic website that shows you the difference between your version and the version you will be using after your upgrade.
Long Running Branch Strategy
The default upgrade strategy that most people use when updating their Rails apps is called the “Long Running Branch” strategy. The Long Running Branch strategy typically followings the approach recommended in the Rails Guide:
- Write tests and make sure they pass.
- Move to the latest patch version after your current version.
- Fix tests and deprecated features.
- Move to the latest patch version of the next minor version.
If you perform a Long Running Branch strategy upgrade, it typically involves checking out a dedicated git branch, usually called Upgrade Rails 61. Then you work on upgrading, fixing, and migrating to the new version. While this upgrading strategy is simple and isolated, it is not without its challenges.
The problems come when other developers need to merge their changes into the system as well. Because you’re working on the same branch for so long, other people are making changes that, when they upgrade their stuff in, it ends up in conflict with your own upgrade.
|Long Running Branch Strategy|
Dual Boot Strategy
Let’s talk about a better strategy: Dual Booting. I think the first time I heard about this strategy was at RailsConf 2016 in Phoenix. Rafael Franca gave a great keynote talk about how Shopify, one of the largest and oldest Rails apps, struggled for almost a full year to upgrade from Rails 4.2 to Rails 5.0. They needed to find a better way and came up with an initial version of the Dual Boot strategy.
You can watch the full keynote presentation here:
The strategy was so successful, they now build against the main Rails git branch. As soon as the next version of stable Rails is released, they can drop it right in and they are off to the races. Shopify’s strategy continued to evolve and improve, until the Dual Booting strategy was officially born.
The Dual Booting strategy was introduced by Andrea Fomera at RailsConf 2022 in her session, entitled “Upgrading Rails, Everyone Can Do It and Here’s How.” While the full presentation will eventually be available here, I have summarized many of her main points below.
What is Dual Booting?
Dual booting is running your application with two sets of Gemfile.lock files and an easy way to toggle between them. With this setup, you can run separate CI jobs for the current and next versions of Rails. This lets you resolve any dependency or code issues for the next Rails version while still keeping on the main git branch. You can even deploy to production (like a “canary in the coal mines”) with a small portion of your production servers running the next Rails version. If it all passes and you don’t discover any problems, you can then flip all of your servers over to the next Rails version worry-free.
|Dual Booting Strategy|
How Do I Start Dual Booting?
Upgrading your Rails using the Dual Booting strategy requires you to create two Gemfiles: one with your current Rails system and one with the upgraded Rails dependencies. In order to accomplish this step, there are two different methods you can utilize:
There are two different ways you can start upgrading using the Dual Booting strategy:
1. The Simple Gemfile.
The first option is creating two different Gemfiles, each with their own Rails version, and then using the BUNDLE_GEMFILE=Gemfile.next to switch between them. However, this creates a lot of duplications, so you have to create a third Gemfile for shared gems. But now you have weird edge cases and three different Gemfiles that you have to remember to update correctly. It’s a headache. The upside to this strategy is that it doesn’t require any monkey patching.
2. The “if” Switch.
The more preferred way is to create an “if” switch in the Gemfile itself. Making the toggle an environment variable makes it easy to switch on local deploy. However, the downside is, again, you have to monkey patch. Fortunately, there are a couple of Gems available to do the monkey patch for you if you choose to go this route:
Both of these upgrade Gems accomplish the same thing; it just depends on your preferences for upgrading. Next-rails is a bit more future rich, but BootBoot is easier to implement.
Creating a Monkey Patch Using BootBoot
Both BootBoot and Next_Rails accomplish the same thing; however, I personally prefer to use BootBoot, as I find that it is easier to implement. To implement the monkey patch using BootBoot, you can follow these steps:
- Add BootBoot to your Gemfile and an “if” switch to the Rails version. You can name this variable whatever you want.
- Install the BootBoot plug-in with bundle install and run bundle BootBoot to upgrade your Gemfile. This adds a shim into your Gemfile.
- Running that bundled BootBoot command also creates a Gemfile_next.lock file. At the moment, it’s just a pure copy of your Gemfile.lock. To install the next version of Rails, you need to run the DEPENDENCIES_NEXT=1 bundle upgrade in the console. This gets locked into the Gemfile_next.lock file, and that’s where the new version of Rails gets installed.
You should now have three separate files:
- A single Gemfile
Lastly, you’ll have to sprinkle “if” switches throughout your code for things that don’t work in both versions. You’ll have to remember to clean these up when you deploy. But that’s the cool thing about this strategy- it’s on the same code base as a homogenous whole; you don’t need to worry about mismatched dependencies or who’s on what branch. Plus, you can easily switch between Rails versions with just a simple enviro-variable flip.
Continuously Dual Booting
Now that you’re able to switch Rails versions on the fly, you can do cool things, such as opening a separate Continuous Integration job for your unittests that runs the next version of Rails. And because it’s all on the same git branch in the code base, you can split up the upgrade into small tasks for the team. This allows you to preemptively fix things a little bit at a time so that when you are ready to upgrade, you can just flip a switch and be running the next version of Rails.
Where before, with the Long Running Branch strategy, a single developer would typically disappear into the upgrade for a month at a time, you are now able to complete upgrades as a team with minimal effort. Overall, this is a huge step for Rails, and one that we at Grio are already deploying on our Rails projects.