Every year, we spend some of our time upgrading Ruby on Rails applications. Some of them have been created by us (and thus we know everything about them) and some of them haven't. In any case, we have the same approach despite their difference (dependencies, versions, etc.).
Rails is a framework that moves quite fast. Following the Semantic Versioning, the Rails core team releases a major version every 3 years on average average, a minor version update every year on average and about a dozen bugfixes versions a year. With every major release of the framework, an oldest major release of the framework reaches its EOL (End Of Life). For example, when Rails 7 was released, Rails 5 reached its EOL. This means it won't receive any update newer versions will receive (in this case Rails 6 and Rails 7). So having an old Rails version will mean your application won't receive patches for security and severe security issues (as per the maintenance policy), and might be vulnerable to attackers. Also, not upgrading Rails often means that it will be eventually harder to upgrade rails in the future.
At the same time, Ruby on Rails versions support specific Ruby versions. For instance if you want to upgrade your Rails 5 application to Rails 6 and you're currently running on Ruby 2.3, you need to upgrade to at least Ruby 2.5 (as per this table). But it's not that simple. Ruby has it's own release cycle and their different versions have their own EOL. In this case, Ruby 2.5 has already reached its EOL (See ruby branches), so it's recommended to upgrade to a newer version.
The first step of the upgrade is to gather information. Digging into the application to answer to the following questions:
The answers to these questions will help us determine what are the latest versions we can upgrade the interpreter, the framework and the dependencies. It's really important to note that the infrastructure where the app is running will determine the latest versions we can upgrade to. ie. it's not the same to deploy a container, a heroku app, a AWS Elastic Beanstalk app, or any other vendor specific setup.
After gathering this information, we will try to upgrade the interpreter, the framework and the dependencies to their latest versions possible, without compromising the functionality of the application (again, tests are a great plus). We will do so by upgrading one Rails version at a time. There's a little process for that:
These steps need to be repeated as many times as Rails versions we need to upgrade.
While most upgrades are the same for a local environment, the differ in a key part of the architecture: the infrastructure. It's not the same to deploy a rails app to bare metal having to provision everything (operating system users, web server, application server, database server, interpreter, etc) and scale manually, and deploying to a platform such as AWS Elastic Beanstalk, Heroku or other vendors where provisioning and scaling is handled by the platform.
Upgrading apps running on bare metal usually takes a bit longer as building new environment might involve manual provisioning. It's extremely difficult to even try and describe how to do it as applications running on bare metal have as many setups as one can imagine, so it's up to the readers to figure out how to upgrade the infrastructure of their application.
AWS Elastic Beanstalk comes with a constraint: AWS Elastic Beanstalk for Ruby supports two Ruby versions at the moment: Ruby 2.7 and Ruby 3.0. So these are the maximum ruby versions you can aspire to. It's recommended to upgrade to the latest one.
Normally, if you use Elastic Beanstalk for your application, you would upgrade your app often as it only supports two versions of the interpreter. And such upgrades consist mostly of upgrading the platform the application is running on. As part of the upgrade, we will need a different environment. Ideally a staging environment using the same database as the current staging database.
Upgrading a Heroku application is probably the easiest of these examples as it doesn't require any action other than upgrading the code. Heroku is smart and it knows exactly what version of Ruby you are upgrading to (by reading the .ruby-version
file).
The outcome of an upgrade is normally a git branch containing the upgrade, and a new environment contaning such branch, ready to be merged.
Stay tuned for the next posts in the series where we'll talk about processes and actions to help making upgrades smaller, less painful and with reduced risks.