Introduction
Hey, I’m Nuzair (Red), a Ruby on Rails developer at Money Forward. Recently, I tackled a project that required upgrading Rails and Ruby versions. In this article, I’ll share how we used Bootboot to manage multiple gem versions and ensure a smooth upgrade process.
Overview
Managing multiple versions of gems in a single Ruby codebase can be challenging, especially when upgrading major components like Rails or Ruby. Bootboot simplifies this process by allowing developers to maintain and test different versions of dependencies without disrupting existing functionality. This blog outlines how we utilized Bootboot to upgrade Rails and Ruby smoothly.
Introduction to Bootboot
Bootboot, developed by Shopify, is a Bundler plugin designed to facilitate dual booting in Ruby applications. After setting up Bootboot, it generates a Gemfile_next.lock
file, which can contain a different set of gem versions. This can be controlled by passing the flag DEPENDENCIES_NEXT = 1
as an environment variable. For detailed information, refer to the Bootboot documentation.
Setting Up Bootboot
Prerequisites
To set up Bootboot, follow these steps:
1. Add the plugin to your Gemfile:
plugin 'bootboot', '~> 0.2.2'
2. Run the following commands to install and configure Bootboot:
bundle install && bundle bootboot
This will modify your Gemfile and create a Gemfile_next.lock
. The modified Gemfile should look like this:
plugin 'bootboot', '~> 0.2.2'
Plugin.send(:load_plugin, 'bootboot') if Plugin.installed?('bootboot')
if ENV['DEPENDENCIES_NEXT'].to_i == 1 && Plugin.installed?('bootboot')
puts 'Installing next dependencies...'
enable_dual_booting
else
puts 'Installing current dependencies...'
end
I added the puts
statements as they help in identifying which dependencies are being installed in the build logs.
Case Study: Upgrading Rails 6 to Rails 7
After setting up Bootboot, the Gemfile was modified to support both Rails 6 and Rails 7:
if ENV['DEPENDENCIES_NEXT'].to_i == 1 && Plugin.installed?('bootboot')
puts 'Loading Rails 7 dependencies...'
enable_dual_booting
gem 'rails', '~> 7.1.3'
gem 'rackup'
gem 'vite_rails'
else
gem 'rails', '~> 6.1.7.7'
gem 'net-imap', require: false
gem 'net-pop', require: false
gem 'net-smtp', require: false
gem 'webpacker', '~> 5.4'
group :development, :test do
gem 'pry-rails' # will be deprecated for Rails 7
end
end
We upgraded to Rails 7 and included gems specific to both Rails versions. We replaced webpacker
with vite
. Additionally, we removed obsolete gems for Rails 7, such as net-imap
, net-pop
, net-smtp
, and pry-rails
.
Running the Application
To run the Rails server for different versions:
For Rails 6:
bundle install
bundle exec rails s
bin/webpack-dev-server
bundle exec rspec [test/files/if/needed]
For Rails 7:
DEPENDENCIES_NEXT=1 bundle install
DEPENDENCIES_NEXT=1 bundle exec rails s
DEPENDENCIES_NEXT=1 bin/vite
DEPENDENCIES_NEXT=1 bundle exec rspec [test/files/if/needed]
Set DEPENDENCIES_NEXT=1
to enable Rails 7 code. Note that vite
is not available for Rails 6, causing an error if attempted.
Adding New Gems
When adding new gems, ensure compatibility with Rails 7:
if ENV['DEPENDENCIES_NEXT'].to_i == 1 && Plugin.installed?('bootboot')
puts 'Loading Rails 7 dependencies...'
enable_dual_booting
gem 'example_gem', 'v3'
else
gem 'example_gem', 'v2'
gem 'gem_only_needed_for_rails_6'
end
Common gems should be compatible with both Rails versions. If a gem is specific to a version, include it within the conditional block.
Important Commands
To update gems in both Gemfiles:
bundle install
DEPENDENCIES_NEXT=1 bundle install
Note: If you use Dependabot, it will not update Gemfile_next.lock
. Manual updates are required for that.
Writing Code and Handling Deprecations
Use the DEPENDENCIES_NEXT
environment flag to manage differences between Rails versions:
def preload_association(records)
if ENV['DEPENDENCIES_NEXT'].to_i == 1
::ActiveRecord::Associations::Preloader.new(records: records.flatten, associations: [@association_name]).call
else
::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
end
end
Here, we handle deprecations in ActiveRecord’s Preloader
class based on the Rails version. This approach ensures compatibility with both versions. Make sure the method’s results are consistent across Rails 6 and 7.
Updating CI for Rails 7
We also modified our CI to run tests for Rails 7 along with Rails 6 tests to ensure compatibility and make sure changes in Rails 6 do not break Rails 7.
Build and Release
When deploying, ensure the DEPENDENCIES_NEXT
environment flag is set during build time and runtime. This ensures that the correct dependencies are loaded based on the Rails version, which is crucial for a successful deployment and depends on your deployment strategy.
Additional Tips
Middleware Configurations
Use Bootboot to configure middlewares differently based on version.
Rails Application Default Configs
Load default configs as needed. We fixed an issue with remote forms this way.
Frontend Changes
Since we moved from webpacker
to vite
, we adjusted file setup based on the environment variable so that Rails views and assets work correctly.
Case Study: Upgrading Ruby 3.2.2 to Ruby 3.3.3
Similar to the Rails upgrade, we upgraded Ruby:
if ENV['DEPENDENCIES_NEXT'].to_i == 1 && Plugin.installed?('bootboot')
puts 'Installing next dependencies...'
enable_dual_booting
ruby '3.3.3'
else
puts 'Installing current dependencies...'
ruby '3.2.2'
end
Bootboot has limitations with the .ruby-version
file. Because most of our pipelines use .ruby-version
, we had to create the file manually during build and testing based on the flag.
Conclusion
The Bootboot plugin is a powerful tool for managing multiple versions of gems within a single codebase. Its dual-booting capability allows for smooth upgrades and thorough testing without disrupting production. Our team successfully used Bootboot to upgrade Rails and Ruby, maintaining stability and addressing deprecations effectively. It also helped us reduce review times for PRs as the changes were incremental and well-tested.
I hope this guide helps you navigate similar upgrades not only Rails and Ruby but also other dependencies in your projects. Thank you and happy coding! 🚀