To safely update the Ruby and Rails version, your app should have a proper test suite you can rely on. But even with a great test suite, I always recommend running a rake task, starting the rails console and starting the rails server and using your app in the browser, before opening a PR and asking for reviews.
Do it incrementally, don't skip any minor versions.
Which means, if your app is still on Ruby 2.7.x, then first update to the latest 2.7.X version, before updating to the latest 3.0.Y version and only when this has been done successfully, upgrade to the latest 3.1.Z version.
Ideally you fix all deprecation warnings before switching to the next higher version.
Ruby has good documentation about the changes from version to version:
- https://rubyreferences.github.io/rubychanges/2.7.html
- https://rubyreferences.github.io/rubychanges/3.0.html
- https://rubyreferences.github.io/rubychanges/3.1.html
Though you might end up googling or checking StackOverflow for specific refactorings anyway.
Doing updates incrementally also means deploying them to staging and production and only updating to the next version when no issues occured.
Ensure you update the Ruby version everywhere:
- .ruby-version file
- Gemfile (optional, not all Gemfiles include it)
- deployment scripts
- CI workflows
- .rubocop.yml
- Overview of all Ruby releases: https://www.ruby-lang.org/en/downloads/releases/
- End-of-life (EOL) dates of Ruby versions: https://endoflife.date/ruby
Before you install a new Ruby version (I use
ruby-install
to install new versions andchruby
to switch Ruby versions) you should update your system to ensure it has the latest compilers and libraries available. On my Mac I would runbrew update && brew upgrade
before I begin.
Do it incrementally, don't skip any minor versions.
Same as with Ruby, and also Rails has a really good officia upgrade guide you can follow - just repeat all the steps for every new version:
There are also really good unofficial guides out there to teach you about necessary refactoring steps, like:
- https://lilyreile.medium.com/rails-5-2-new-framework-defaults-what-they-do-and-how-to-safely-uncomment-them-af931d27afc4
- https://lilyreile.medium.com/rails-6-0-new-framework-defaults-what-they-do-and-how-to-safely-uncomment-them-586146f371e8
- https://lilyreile.medium.com/rails-6-1-new-framework-defaults-what-they-do-and-how-to-safely-uncomment-them-c546b70f0c5e
- Overview of all Rails releases: https://rubygems.org/gems/rails/versions
- End-of-life (EOL) dates of Rails versions: https://endoflife.date/rails
To be able to use newer versions of Ruby and Rails often means you have to update - or even replace - some of the gems you use. Before you start any other updates, you should ensure your dependencies are on the most recent version available.
First set the allowed versions for all your gems in the Gemfile with the squiggly operator. e.g. instead of just declaring:
gem "devise"
setgem "devise", "~> 4.7"
- which behaves the same as
gem "devise", ">= 4.7, < 5.0"
.
I always recommend to use ~> MAJOR.MINOR
as this proofed to be flexible enough for regular gem updates while limiting the version upgrades to (most-likely) non-breaking changes. At least when the gem authors properly follow Semantic Versioning (SemVer).
To find out which version your app is using and therefore which limit you should set, check Gemfile.lock.
Don't declare ~> MAJOR.MINOR.PATCH
version, as this limits updates too much and you could just set the fixed version, when you are afraid of updates of this gem.
Some gems where patch level updates are known to cause issues, I set to a fixed version, in particular this one:
gem "rails", "7.0.4"
which behaves the same way asgem "rails", "= 7.0.4"
Other gems which are very stable, you might allow to update freely, by either not declaring any version limitation or an open ended one, like: ">= 1.10"
, but I would generally advise against this.
Theoretically you could run bundle update
and update all the gems - at least to the maximum versions you declare in the step above. When you have not done this in a long time, it will lead to too many changes though, which will make it hard to determine which update might lead to issues.
So instead run bundle outdated
to get an impression which gems have newer version and then run bundle update gem_name another_gem_name
to update the gems in smaller groups. After every update check the parts of the application that might have changed and run the whole test suite.
The bundle outdated
output will also show you versions which you can only update to after you changed the version limitation in the Gemfile. Now that this is a dedicated, deliberate step, you are aware when you update a gem to a version that has potentially breaking changes and you will put the extra effort in to ensure the update didn't break current behavior.
Often those newer gem versions only work on newer versions of Ruby or Rails - which means during the update of the whole application you will jump between updating dependencies, updating Ruby and updating Rails, if you are updating multiple versions of Ruby or Rails at once.
After you've reached the latest Ruby and Rails version (or the ones you were aiming for) I'd recommend to update in the next step all the gems.