Development

Crucial Steps When Upgrading an Application From Rails 3 to Rails 4

By March 8, 2017 No Comments

The web ecosystem changes incredibly rapidly, especially with regards to essential security. Staying current on updates for web applications is therefore incredibly important. Nevertheless, it can be very easy to fall behind on updates especially for stable production applications. In Rails, upgrading to a new major version is a difficult, time consuming process. Developers are reluctant to begin this process for fear of introducing breaking changes into an otherwise functioning application. Clients also find it difficult to justify the budget on something that adds no new functionality and seemingly little additional value.

Here are steps to take when upgrading your application from Rails 3 to Rails 4:

With the release of Rails 5.0 in June 2016, official support for Rails 3.2 ended. This means that Rails 3 no longer receives updates for bug fixes or critical security issues. At Web Ascender we recently completed the process of upgrading a medium to large project from Rails 3.2 to Rails 4.2. This project comes in at approximately 150k lines of code, includes about 75 gems and has about 1000 active users. In this post we’ll look in depth at the process of upgrading a project from Rails 3 to 4. We’ll discuss helpful strategies and point out some particularly difficult bugs and problems we encountered.

Before you begin the upgrading process

First, without a comprehensive test suite, I would argue that upgrading a medium to large project across a major version change of Rails is impossible. As a rough estimate, your tests should have at least 50% total coverage and you should strive for as close to 100% as possible. Your tests should operate at all levels throughout your application from unit tests to full feature and integration tests.

You’ll also want to start a new git branch. Upgrading Rails is a lengthy process that will most likely introduce changes across your entire application. You will want to keep your other working branches clean and open. Finish the upgrade path entirely and spend the necessary time on testing and QA before merging anything back into your main branches.

Keep in mind this guide assumes you are using a ruby version manager (rbenv or rvm) and bundler for your gems.

Finally, in this guide we will be starting at Rails 3.2.22. If you are on an earlier version of Rails 3 you can use the same general methodology described in this post to first upgrade to Rails 3.2.22.

The Rails 3 to Rails 4 Upgrade Process

Rails maintains a helpful upgrade guide. You should use this and upgrade according to the path laid out in the guide. We will upgrade as recommended first from 3.2 to 4.0, next from 4.0 to 4.1 and finally from 4.1 to 4.2. Before starting on Rails upgrades I recommend you first upgrade to a modern version of ruby. For Rails 4.2 you should be using ruby 2.3.3. If you are on Rails 3 you are most likely using ruby 1.9 or 2.0. Start by upgrading your project’s ruby version first.

The complete upgrade path is as follows assuming you are currently on ruby 1.9

Ruby 1.9 -> Ruby 2.0
Ruby 2.0 -> Ruby 2.1
Ruby 2.1 -> Ruby 2.2
Ruby 2.2 -> Ruby 2.3
Rails 3.2.22 -> Rails 4.0.13
Rails 4.0.13 -> Rails 4.1.16
Rails 4.1.16 -> Rails 4.2.7.1

For each individual upgrade we go through the following steps

1. Read through either the relevent section of the Rails upgrade guide or the Ruby version release notes. Make the suggested changes.

From this point on you are committed to the upgrade, your application will be broken until you complete all of the steps for this upgrade.

2. Upgrade your Rails/Ruby version and get your gemfile to successfully bundle.

Either update your ruby version file (rbenv) or set your Rails gem to the next target version. Remove the version number on all of your other gems except those you are certain need to be locked. If for example you are using MySQL, the mysql2 gem should stay locked to version 0.3.18 due to a Rails 4.x.x compatability issue. I also found it helpful to keep the ‘json’ gem locked to version 1.8.6.

Run the bundle command and let bundler attempt to resolve gem versions and compatibility.

Your gemfile will probably not bundle successfully on the first try. Lock specific gem versions to resolve compatibility issues where necessary. For more obscure errors you will probably need to go hunting through the stack trace. In very extreme cases you may find that a gem needs to be removed or replaced.

3. Get your local server to start.

This is where you will see lower level failures such as load errors, dependency errors etc. You may see issues with the more fundamental rails classes like active support. You may run into more gem compatibility issues here as well. Use the error prompts and stack traces to resolve problems as they arise.

4. Get your test suite to run

Before getting your test suite to pass, the next step is to get your test suite to run. This involves the ruby interpreter going through your code. At this step you will see fundamental ruby syntax errors, no method errors and other similar problems.

5. Get your test suite to pass

Next get all of your tests to pass. This is where you will see errors arising from changes to Rails syntax, logic and methods. At this point you may start to see subtle bugs that result in changed data/output in addition to hard errors and crashes.

6. Address all deprecation warnings

Once your test suite is passing address all deprecation warnings returned by Rails and any of your project gems.

After completing these steps, you have completed one upgrade and your project is back to a mostly stable state. When ready, move onto the next upgrade and repeat the above steps.

Completing the Rails 3 to Rails 4 upgrade process with QA and testing

Eventually you will make it to Rails 4.2.7.1. The next step is comprehensive testing and QA. Depending on how confident you are with your test suite, for a production application you should ideally go through your project and manually test every piece of functionality. Try to hit every action and every controller if possible. Try to test together with a coworker if possible. Pay special attention to larger gems such as paperclip and devise.

When you are satisfied with your testing deploy your upgrade to an environment that is set up to match what you have on production. Make sure your deployment is successful and give your application one more quick test to look for any obvious problems. Finally, deploy the upgrade to your production environment. Make sure you have some sort of exception monitoring in place and be prepared to keep a closer eye on your project and your users for the next few days.

Issues, errors, hints and warnings encountered during the Rails 3 to Rails 4 upgrade

There are many changes and notices listed on the Rails upgrade guide. You should read through and address each one. In this section we will look at which of these changes had the largest impact on our application and provide some additional details. We will also examine a few other issues we ran into that were not listed in the official guide.

Strong parameters and mass assignment security

Rails 4 introduced strong parameters which replaced the old attr_protected and attr_accessible methods for mass assignment security. There are two options available to address this.

1. Remove all such methods from your models and write the equivalent strong param methods in the corresponding controllers.

2. Use the protected_attributes gem to keep the old methods.

The second option is definitely faster but there are two things to be aware of. First, the protected_attributes gem is no longer supported so eventually we will need to go through option 1 anyway. You might be better off just getting it done now. Also be aware that the protected_attributes gem adds mass assignment security to every model in your application. This includes classes and models from your other gems as well. In some cases you may start seeing errors related to mass assignment. In this case the simplest approach is probably to just open up the ruby class and add the necessary attr_protected or attr_accessible method.

  • We encountered this issue with ActiveRecord::SessionStore and solved it by adding the following line to our application.rb file.

ActiveRecord::SessionStore::Session.attr_accessible :data, :session_id

  • Includes can no longer be used as an intrinsic left-join. You need to use references for query conditions on associations.

This will no longer work…

User.includes(:posts).where(“posts.name = ‘foo’”)

Change the above query as follows…

User.includes(:posts).where(“posts.name = ‘foo’”).references(:posts)

  • The syntax for scopes has changed. Scopes must be given a callable object (proc/lambda). The default_scope method now accepts a block.

scope :active, where(status: ‘active’)
scope :active, -> {where(status: ‘active’)}

default_scope order(:name)
default_scope {order(:name)}

  • Many finder methods have been removed such as find_all_by and find_last_by etc.

Assets and the asset pipeline

The assets group has been removed from the gemfile, you can simply move these gems and delete the assets group. Assets are no longer compiled from the lib and vendor folder. Move all assets into the assets folder.

Older gems and assets from the time of Rails 3 tended to use less. The newest versions of sprockets/railties are built for sass and will probably have issues with gems and assets using less. An example are the less-rails and twitter-bootstrap-rails gems used to compile the bootstrap 2 assets. The easiest approach is most likely to just remove all less gems and simply include the minified css assets you need directly in your project and ‘require’ them into your application.css file.

  • therubyracer gem was used often in Rails 3 to embed the V8 javascript runtime for the rails asset pipeline. It has a lot of dependencies and can easily conflict with other gems and system level configurations. You can simply remove this gem and install node on your system. Rails will then use node as the javascript runtime. Remember to make sure node is also installed on your production servers if you are going to take this approach.
  • The default join table naming convention for the has_and_belongs_to_many association has changed. You should specify the table name explicitly using the join_table option.
  • There were many changes to the default conventions and helper functions available in config/routes.rb. When your routes aren’t generating the way you want, use the match function with the via: and as: options. You may end up with a few more lines of code but you should always be able to use the match function to generate routes exactly as intended. Here’s an example of how we re-wrote our dashboard routes to resolve a route naming conflict error.

match “/dashboard” => “dashboard#index”, via: [:get, :post], as: “dashboard_index”

  • The paperclip gem added new required validations. All attachments are required to include both of the following…

validates_attachment_content_type
validates_attachment_file_name

Or you can disable validations using…

do_not_validate_attachment_file_type

Final thoughts

Upgrading a large project to a new version of rails can be difficult and time consuming. You will most likely encounter a wide variety of strange, interesting and esoteric errors. Be patient, take breaks where necessary, utilize coworkers and online resources and allow yourself some momentary celebration at each completed step. Address each error and deprecation one at a time and eventually you will make it to Rails 4.2.7.1.

Web Application Startup Guide

A 30-page ebook that covers positioning, marketing, pricing, and building your startup product, plus more.