Upgrading a project to the latest Ruby on Rails version
Read in 6 minutes
Every time you start a new Ruby on Rails project, you use the latest stable released version. You start coding, a process that can take some time (weeks or months, even). It doesn’t take long, until you can’t upgrade your project to a new Rails version. Why does this happen? How can you avoid this trap?
One step at a time
The first thing you have to keep in mind is that you do have a better chance by doing small upgrades. Are you running Rails 3.2.21 and want to upgrade to Rails 4.2.1? No problem! Your chances of doing it successfully will increase if you first upgrade to 4.0.13, then 4.1.9, and, finally, Rails 4.2.1.
Seems too much work? It is, but remember… there are lots of changes between these releases, with deprecations and even feature removal, but doing it in small doses will make your life way easier.
Understanding Rails versioning scheme
Ruby on Rails follows a versioning scheme based on SemVer. The X.Y.Z
versioning has the following representation:
- Patch
Z
: Only bug fixes, without API changes or new features, except when this is required for implementing a security fix. - Minor
Y
: New features. It may have API changes. Breaks in the API compatibility that were deprecated on previous minor/major releases. - Major
X
: New features, with likely API changes. The difference between minor and major releases is in the API breakage.
When talking about upgrading a Rails version, we mean changing major and minor versions. It would be going from 3.1 to 3.2, and 3.2 to 4.0, and so on. To discover what’s the most recent Rails release, go to Rails’ Rubygems.org page.
Before releasing a new version, beta and release candidate versions are published, so developers can test these versions and report things that shouldn’t stop working. In the end, we’ll have more solid releases.
Updating your project’s source code
Every time you upgrade a project to a newer major version, you have to run rake rails:update
. This will update generated files (like environment files, config/application.rb
and more), applying changes required by that version. This will ask you to review the changes for every different file, which can take a lot of time.
If you’re like most developers, you probably change some generated files, like config/application.rb
and environments/*.rb
. So, in this step, you’ll have to decide what are the changes you want to keep while also applying Rails changes. So, I give you this rule: Never change generated files. Every time you have to change a default configuration, create an initializer file. When you have different configuration for different environments, do it conditionally.
This is how you can configure ActionMailer for production and development environments:
# config/initializers/action_mailer.rb
if Rails.env.development?
# Define settings for mailcatcher
Rails.application.config.action_mailer.tap do |action_mailer|
action_mailer.delivery_method = :smtp
action_mailer.perform_deliveries = true
action_mailer.raise_delivery_errors = false
action_mailer.smtp_settings = {address: "localhost", port: 1025}
end
end
if Rails.env.production?
# Define settings for sendgrid
Rails.application.config.action_mailer.tap do |action_mailer|
action_mailer.delivery_method = :smtp
action_mailer.perform_deliveries = true
action_mailer.raise_delivery_errors = true
action_mailer.smtp_settings = {
address: 'smtp.sendgrid.net',
port: '587',
authentication: :plain,
user_name: ENV['SENDGRID_USERNAME'],
password: ENV['SENDGRID_PASSWORD'],
domain: 'example.com',
enable_starttls_auto: true
}
end
end
This way, you can always apply all changes suggested by rake rails:update
, without even checking the diffs. To make the initializer definition easier, you can use a gem called rails-env. It reduces the noise introduced by those ifs and Rails.application.config
calls. The same initializer would look like this:
# config/initializers/action_mailer.rb
Rails.env.on(:development) do |config|
# Define settings for mailcatcher
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false
config.action_mailer.smtp_settings = {address: "localhost", port: 1025}
end
Rails.env.on(:production) do |config|
# Define settings for sendgrid
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.smtp_settings = {
address: 'smtp.sendgrid.net',
port: '587',
authentication: :plain,
user_name: ENV['SENDGRID_USERNAME'],
password: ENV['SENDGRID_PASSWORD'],
domain: 'example.com',
enable_starttls_auto: true
}
end
Keeping track of changes
The best way of knowing what changes are introduced by a version is by reading the upgrade guide. You’ll find everything you need to add, remove, and/or update, with links to relevant commits, which will contain more links to changelogs.
You also want to follow the official blog, which always publishes articles with more information about the new releases.
Ruby version
Rails releases require specific Ruby versions; usually the latest stable version released is preferred.
- Rails 3+ requires Ruby 1.8.7, which was discontinued on July 31, 2014.
- Rails 4 recommends Ruby 2.0, but requires Ruby 1.9.3 or greater. Ruby 1.9.3 was discontinued on February 23, 2015.
- Rails 5 will require Ruby 2.2.
If you’re running any version below Ruby 2.0, please do migrate as fast as you can. These versions won’t have any new bug and security fixes, so you’re on your own.
Dependencies
Another common problem is that dependencies can break between Rails releases. Be careful about what you use on your project. Not every project will be supported forever by their creators, and you may be tied to dead code.
Avoid using gems for every single feature you need. Do you really want to tie your project to an external dependency that may be written in a hundred lines of code? And please, run away from articles like “Gems you need for every project”.
This doesn’t mean you have to write all the code. Just consider the trade-offs wisely.
Release frequency
How frequently is Rails updated? Let’s check the dates:
- 4.2.0 - Dec 20, 2014
- 4.1.0 - Apr 8, 2014
- 4.0.0 - Jun 25, 2013
- 3.2.0 - Jan 20, 2012
- 3.1.0 - Aug 31, 2011
- 3.0.0 - Aug 29, 2010
As you can see, the time between releases is at least 7 months. Prepare yourself, and save some time to upgrade your projects. Remember that Rails has a strict maintenance policy.
- Bug fixes are applied only to the latest version (unless someone from the Core Team decides to do it, as Rafael França did with Rails 4.1.x).
- Security fixes are applied to the latest two versions.
- Core Team decides what versions will receive severe security fixes, which may or may not include older releases.
It’s important to keep your project up-to-date; that’s the only way you can be sure that you’ll receive bug and security fixes. If you decide using an unmaintained version, it’s your responsibility to fix these bugs, backporting code from newer versions. It’s not an easy task, as you can imagine.
Running your project
It’s time to run your project and discover what’s broken. I don’t even have to say that tests are important on this step and will help you a lot with the upgrading process. If your project has tests, run your suite and fix the code as you go.
Please, read all the error messages. If the error message isn’t clear enough, don’t be lazy: do a Google search. Other people may have had the same problem and you can even find a documented solution. If you can’t find anything, try to isolate the problem; if it’s related to some gem or Rails itself, open an issue with steps on how to reproduce the issue.
If your project doesn’t have tests, there isn’t much you can do. Browse your site, run the most common flows, and fix errors as they show up. One thing you can do is to automate this process by using something like Capybara. This will introduce some test coverage that can even drive your future development, and will help you in a future upgrade.
Wrapping up
Before upgrading a project, you have to understand your motivations:
- Do you need a new feature?
- Is it getting harder and harder to maintain old code or find new libraries that still support the Rails version you’re using?
- Do you have the time to make the upgrade?
- Do you need a bug fix available on this new release?
There are some many other questions that you can ask.
I always keep my projects up-to-date. If you have the time to upgrade, please do it. Being stuck in unmaintained versions is something I always try to avoid. What about you?