Ler blog em Português

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:

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.

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:

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.

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:

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?