-
Notifications
You must be signed in to change notification settings - Fork 25
Rails: Upgrade process
For best results, you should upgrade in three phases:.
- improve your tests
- upgrade gems
- upgrade rails
- upgrade ruby
The better your test coverage. The more likely you will detect issues during upgrades.
Start by checking the pins on your gems. People have different comfort levels when it comes to pinning gems. If everyone followed semantic versioning, I'd recommend pinning that would allow minor and patch release upgrades like this gem 'rails', '~> 7.0'
. In practice, however, many gems—rails included—sometimes include significant changes in even minor version. A more conservative approach is to pin to only allow patch upgrades, like this gem 'rails', '~> 7.0.0'
. This will prevent major and minor upgrades from happening when you run bundle update
— you can evaluate if a major/minor change to a library can be safely applied to your project and change the pin. There are also tools to automate this process; dependabot is one example.
Run bundle update
and then run your tests. If you have good test coverage and are conservatively pinning your gems then this usually goes smoothly. If not, you can refer to the trouble shootings steps in section 2.1.3.
Now we can assess what gems have had major work done since last time we did upgrades. Bundler provides the outdated
command to list out what we currently have vs the latest available for all of our dependencies.
bundle outdated
Example output:
Gem Current Latest Requested Groups
bootsnap 1.12.0 1.15.0 ~> 1.12.0 default
cancancan 3.3.0 3.4.0 ~> 3.3.0 default
font-awesome-sass 6.1.2 6.2.1 ~> 6.1.1 default
libv8-node 16.10.0.0 18.8.0.0
mail 2.7.1 2.8.0 ~> 2.7.1 default
rack 2.2.5 3.0.3
rubocop-rails 2.9.1 2.17.4 ~> 2.9.1 development, test
spring 2.1.1 4.1.0 ~> 2.1 development
spring-watcher-listen 2.0.1 2.1.0 ~> 2.0 development
view_component 2.64.0 2.80.0 ~> 2.64.0 default
webdrivers 5.0.0 5.2.0 ~> 5.0.0 development, test
Additionally, you can use https://railsbump.org to assess which of the gems in your Gemfile support the next version of rails you are planning to upgrade to.
I go through each of these one by one and assess what the changes are. If projects are using semver, then the major versions are the most concerning because they introduce breaking changes. The minor versions should only add backwards compatible behavior. When that is true then of the gems above rack
are the first ones to look at because of the major release difference.
If the changes mention deprecations, check that you're not using any of those deprecated features.
Otherwise, update the pin, bundle, and run your tests.
[DANGER] Not everyone follows semantic versioning!
In practice, your mileage may very. For example, at the time of writing, mail 2.8.0 introduced a config change that made the default options in rails 7.0.4 invalid. The lesson being:there may be gotchas in any version update.
[WARNING] Some gems must match the version of another library/gem.
You do need to be careful about some gems whose versions are not semantic, but instead need to be matched against another project's version. For example, therugged
gem needs to match your system'slibgit2
version. Another example:activerecord-oracle_enhanced-adapter
needs to match the major/minor version ofrails
that you are using.
[INFO] rubygems.org browser quicksearch
I have a browser quicksearch to get to the rubygems page for a gem.It makes looking up details and getting to changelogs that little bit easier.
gem insert_gem_name -> http://rubygems.org/search?utf8=%E2%9C%93&query=%s
This step has multiple phases:
2.1. Upgrade rails gem
2.3 Update configurations
2.4 Update defaults
Double check what major.minor version you are on.
Check what versions are available.
https://rubyonrails.org/category/releases
Change the ruby version pinned in the Gemfile to the next major.minor version of rails that is supported by the ruby that you're using.
rails from->to (ruby)
5.0->5.1 (2.5.x)
5.1->6.0 (2.6.x)
6.0->6.1 (3.0.x)
6.1->7.0 (3.1.x)
Fastruby.io maintains a table of the ruby/rails compatibility: https://www.fastruby.io/blog/ruby/rails/versions/compatibility-table.html
You can skip to the latest patch release for each. It is important to upgrade each and every major/minor version one at a time in order. Each one will introduce deprecation warnings that will help inform you about upcoming breaking changes. By clearing out the deprecation warnings at each version, you avoid running into actual errors that would pop up in future versions. The deprecation warnings are usually a much easier guide to follow than the eventual error you'd get if you ignored them.
Anachronistic mismatch between ruby and rails.
If you’re trying to use an ancient ruby (1.8.7) with a new rails (7.0.0), that won't work. The language is missing features that the library needs.
If you're trying to use a new ruby (3.2.0) with a ancient rails (5.1.0), that won't work. The language has changed in ways that the old library wasn't written for.
Check the compatibility chart! After you've upgraded rails as far as you can according to the chart then you can consider upgrading ruby (step 3)
With your new version in hand, go check the official rails upgrade guide for this version. This will prepare you for the changes specific to the version you're upgrading to as we go through the rest of the process here:
https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
Change the version in the Gemfile. Bundle. Run your tests. Start your server.
Did everything work perfectly?
If so, lucky you. Are you sure? Maybe now is a good time to look at test coverage, or expand your manual testing. Usually there is at least a little thing to fix or tweak.
If not, no worries. Let's start working through it.
If you haven't already, consider reading the upgrade guide and release notes; Keep a particular eye out for any deprications or syntax changes that may affect you. Also look out for any of the warnings or errors you saw when running your tests and server.
This happens when rails or one of its dependencies conflicts with the version requirements of other gems you have pinned (or their dependencies).
Example: https://stackoverflow.com/questions/68799845/bundler-conflict-requirements-for-the-ruby-version
Read through the conflicts and check which one is in conflict with rails. Now go check if that gem has a new version you can upgrade to that supports the version of rails you're upgrading to.
These messages are very helpful. You can usually go swap out the syntax right now. It’s good to read the docs depending on how complex the change appears.
Examples:
- DEPRECATION WARNING: TimeWithZone#to_s(:short) is deprecated. Please use TimeWithZone#to_fs(:short) instead. (class:line)
- DEPRECATION WARNING: Calling
<<
to an ActiveModel::Errors message array in order to add an error is deprecated. Please callActiveModel::Errors#add
instead. (class:line)
Unfortunately now we're into vague territory. It'll depend on the particular error.
Is the error related to a specific dependency? Are you sure the version you're using is compatible with this version of rails? If there is a new version that explicitly supports the new rails, upgrade to it. If not, maybe this gem hasn't been updated to work with the new rails changes yet. Have a look at the github issues for the gem, and google the error. If this appears to be something entirely new to the internet, maybe you should open an issue on the project with a detailed error message including the specific errors and any other relevant context. If you are able to find a workaround, please share it on the github issue. If you are able to provide a PR to fix the issue, fantastic. Sometimes you may be the first person in the world to encounter an issue — but not usually. That's why we start with searching.
You might also consider whether or not this dependency is still needed, and consider getting rid of it if you can. Every dependency needs to be worth its long term maintenance costs. Sometimes, external gems can be replaced with new rails functionality. These decisions are, of course, highly situational, based on the complexity of the dependency and how widespread its use is.
You can preview the config differences between the version you're currently on and the one you're upgrading to with https://railsdiff.org/
Thanks to git, I like to overwrite my local configs and then diff them using a diff tool. Before you start, make sure you are fully committed to your project and have a clean git status.
You can overwrite your configs with the default generated ones by running bin/rails app:update
and say Y (yes) to all of the prompts.
Now, you can use your favorite git-capable merge tool [^1] to compare your configs to the default generated configs.
The more you move configurations out of config/application.rb into config/initializers/, the fewer configurations you’ll need to review as part of this diff.
The most common files that I have changes in are these:
- config/routes.rb
- config/application.rb
- config/environments/*
Go through each file, one by one, carefully and move your configs back into place. I try to keep only these configs that I absolutely need, modifying the defaults as minimally as possible and adding additional configs at the bottom. This approach has left me with config files that diff easily against the defaults.
This makes quick work of highlighting any new configurations that rails has added, and makes it clear which of your configs that were originally default configurations are not any longer. You can take your time here to learn about each config.
After you've fixed all your configs. You may also want to run a bin/rails db:migrate
before you commit -- app:update often adds some migrations to tweak the active_storage tables, or other rails internal tables.
Ok, now double check all your tests, and run the server again. Make sure everything is looking good.
If so, great! If not, time to dig into the documentation related to the configurations that changed.
^1: Git capable merge tools:
Linux: vimdiff, meld
Macos: kaleidoscope
Windows: araxis merge
The final step in upgrading rails is changing to the new defaults, if you can. Rails handles this process very clearly. From the bin/rails app:update
step, you should have a config/initializers/new_framework_defaults_7_0.rb corresponding to whatever rails version you are upgrading to. This file does nothing by default, but it is full of comments containing all the new default configurations. Reading it will tell you how a brand new rails application would behave. Uncommenting the configs here will update your application's behavior one default configuration at a time. Depending on the changes, you may want to do these very slowly or very fast. Test and run your server as frequently as you feel comfortable while updating these.
If you are ready to use ALL of the default configurations for the new rails version, then you can delete the new_framework_defaults file and update the config.load_defaults '7.0'
in your config/application.rb file. This will in one statement change ALL of the configurations to the new defaults.
Again, run all your tests, run your server, see how things are.
Now, then: have you upgraded to the latest rails version that is supported by the ruby you're on? If not, repeat steps 1 and 2 until you have. Only then proceed to step 3.
Ok, rails is updated as far as it can be for the version of ruby you have. Now you're ready to upgrade ruby itself.
If you're using a ruby version manager (e.g. rbenv/rbenv/asdf), this should be straightforward. Install the new version. Install bundler. Bundle the project, and try your tests and server.
If you're using docker, you can update your Dockerfile accordingly and rebuild.
Now is a good time to refresh your memory on the features of the new version of ruby.
https://www.ruby-lang.org/en/downloads/releases/
Ok! Run your tests and your server and see how you are doing. There may be language-level deprecation warnings. There may be syntax changes that you need to accommodate.
Testing multiple rubies.
-
Gitlab: https://github.com/outcoldman/docker-gitlab-ci-multi-runner-ruby
-
Github: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-ruby
Testing multiple rails versions.