The Road to Rails 3: make your Rails 2.3 project more Rails 3 oriented

With the first Rails 3 Release Candidate available in a few days and the final version just around the corner, it's time to start thinking in The Rails 3 way. Many developers, including myself, already started to work with Rails 3 and many others are tracking the changes, waiting for the right time to upgrade their Rails 2 applications.

There are already thousands of articles talking about the most cool Rails 3 features so I will skip that part in this article. After all, I already introduced a couple of Rails 3 libraries. I'd like this article to be something different, embracing a more practical style.

I have been working with Rails 3 since the first beta version and all my Rails applications, except one, are now running Rails 3 with Ruby 1.8.7 or Ruby 1.9.2. Trust me, upgrading an application to Rails 3 can be a real hassle if you don't follow the right way, especially for complex applications.

The goal of this article is to feature a list of tips and changes you can already introduce in your Rails 2 application to anticipate the migration and make your code more compatible with Rails 3. The more steps you follow, the less problems you are likely to encounter when upgrading the codebase to Rails 3.

Kudos to this StackOverflow question for inspiring me this post.

Prerequisites

This section includes some highly recommended prerequisites your application and environment should meet if you want to take the way to Rails 3. If you care about your happiness, don't try to upgrade to Rails 3 unless all the prerequisites are satisfied.

Tests

I will repeat it until the end of the world: don't try to work with Ruby or Rails without using unit, functional and integration tests. You have several different choices: Test::Unit, Shoulda, Cucumber, RSpec… pick one and create your test suites as long as you add features or fix bugs.

Don't try to upgrade to Rails 3 unless you have a reasonable code coverage. And this is not because tests are cool or make you feel cool, but because Rails 3 changes so many aspects of your Rails application that upgrading without using your tests will be really painful.

Also, be sure to test your view helpers. Rails 3 adds string escaping by default and many of your Rails helpers might require a fix.

Ruby 1.8.7 or 1.9.2

Rails 3 no longer works with Ruby 1.8.6. You might also want interested to know that Ruby 1.9.1 is not officially supported and Ruby 1.9.2 is the first (and the only) choice if you want to take advantage of Ruby 1.9 features.

Don't fall into the trap: Ruby 1.9.1 seem work at a first glance, as long as you don't use some incompatible features such as the #truncate helper, which relies on ActiveSupport::Multibyte::Chars, which doesn't play very nice with Ruby 1.9.1.

Mandatory Changes

This section includes incompatible changes in the Rails 3 codebase, new features or deprecated behaviors you should avoid to facilitate the upgrade to Rails 3. I call this section "mandatory" because, skipping one of these tasks might prevent your Rails 3 application from loading or running properly.

Use Bundler to manage your Gem dependencies

Unless you lived in a cave for the last year, you probably heard about Bundler. Bundler is the new way to manage Gem dependencies in Rails 3 applications. Bundler is also quickly becoming the new Ruby standard to package and require external Gems in a Ruby project.

What you might not know, is that you can start using Bundler with Rails 2.3 right now. Just follow these steps to override the Rails 2.3 gem handling and replace it with support for Bundler.

The Bundler website contains all the information you need to know about Bundler and the Gemfile specification file.

Unobtrusive JavaScript

Unobtrusive JavaScript is one of the biggest changes on the frontend side of the upcoming Rails 3 version.

Rails 3 is no longer Prototype-oriented, Rails 3 code is now JavaScript framework agnostic and there are many incompatible changes that require your attention. Learn more about Unobtrusive JavaScript in Rails 3.

Start converting your JavaScript **right now** and avoid using deprecated helper so that you won't have to change all your code later when switching to Rails 3.

Object#returning

Did you know that Object#returning has been removed in Rails 3? Replace your code with Object#tap as soon as possible.

Beware of HTML code in YAML locale files

Do you use HTML in your YAML locale files? Make sure the key ends with _html or Rails 3 will escape it.

Rails Metal has been removed

Since Rails 3 is closer to Rack, the Metal abstraction is no longer needed. The Rails Metal feature has been removed from Rails 3.

You can read the commit message to have an explanation of what to do with your existing Metals.

Only ActiveRecord fixtures in the test/fixtures folder

Commit 8ec085bf1804770a547894967fcdee24087fda87 breaks the usage of the fixtures folder for non-model fixtures. If you were used to place fixture files for your custom libraries in the test/fixtures directory, move them to an other folder (e.g. test/files).

Rails error_messages_for, error_message_on, … helpers

Commit cd79a4617421f1b66e905f5da84ff28004e2bedd removes input, form, error_messages_for and error_message_on from the framework. The helpers are still available in a plugin called dynamic_form, but I strongly encourage you to update your code in order to avoid using the plugin. Ryan Bates demonstrates a possible approach.

Optional Changes

This section includes changes for which Rails 3 provides a deprecation warning or fallback. For this reason, you are not forced to complete all these tasks before upgrading to Rails 3. However, I strongly suggest you to review all the steps and change the code before upgrading to Rails 3 to reduce the number of warnings in your console and log file.

RAILS_ROOT, RAILS_ENV, and RAILS_DEFAULT_LOGGER constants are now deprecated

Railties, one of the Gems part of the Rails 3 framework, now deprecates the following global constants:

  • RAILS_ROOT in favour of Rails.root
  • RAILS_ENV in favour of Rails.env
  • RAILS_DEFAULT_LOGGER in favour of Rails.logger

Learn more.

Avoid legacy ActiveRecord::Base#find([:all | :first | :last]) statements

You might have heard about the changes in the Active Record query interface for Rails 3. Here's an interesting quote.

Currently ActiveRecord provides the following finder methods:

  • find(id_or_array_of_ids, options)
  • find(:first, options)
  • find(:all, options)
  • first(options)
  • all(options)
  • update_all(updates, conditions, options)

and the following calculation methods:

  • count(column, options)
  • average(column, options)
  • minimum(column, options)
  • maximum(column, options)
  • sum(column, options)
  • calculate(operation, column, options)

Starting with Rails 3, supplying any option to the methods above will be deprecated. Support for supplying options will be removed from Rails 3.2. Moreover, find(:first) and find(:all) (without any options) are also being deprecated in favour of first and all.

If you have any code using the legacy #find(:all), #find(:first) or #find(:last) methods, start converting them now into the corresponding #all, #first, and #last alternatives. You can pass the options to the method or use anonymous/named scopes.

# deprecated statement
Post.find(:all, :conditions => { :approved => true })

# better version
Post.all(:conditions => { :approved => true })

# best version (1)
named_scope :approved, :conditions => { :approved => true }
Post.approved.all

# best version (2)
Post.scoped(:conditions => { :approved => true }).all

Simulating the new Active Record API

ActiveRecord in Rails 3 will expose a brand new API. You can try to use a very similar approach in Rails 2.3 using named_scopes.

For instance, here's a few named_scopes which simulates Rails 3 features.

named_scope :limit, lambda { |limit| { :limit => limit } }
named_scope :select, lambda { |select| { :select => select } }
named_scope :where, lambda { |conditions| { :conditions => conditions } }

You can create a shared module with all the corresponding Rails 3 new methods implemented in Rails 2 and start using the new syntax as soon as possible. Once switched to Rails 3, simply remove the module inclusion to have your code ready for ActiveRecord 3.

Update 2010-07-26: thanks to Ruby5 I just discovered that someone already created a library to simulate Rails 3 API, called fake_arel.

Rails XSS protection

In order to protect the application from XSS attacks, Rails 3 will automatically escape any content** that does not originate from inside of Rails itself**. This is a very important, non compatible, change which may seriously break your application and the existing view helpers.

Fortunately, you can already experiment the default escaping with Rails 2.3 thanks to the rails_xss plugin. This plugin replaces the default ERB template handlers with erubis, and switches the behaviour to escape by default rather than requiring you to escape. This is consistent with the behaviour in Rails 3.0.

You are encouraged to try it as soon as possible.

Plugin tasks and initialization

Rake tasks in vendor/plugins/PLUGIN/tasks are deprecated, as well the vendor/plugin/PLUGIN/rails/init.rb file. Use lib/tasks and Railties instead. lib/tasks already works in Rails 2, Railties is a Rails 3 feature but you can use it in combination with the legacy init.rb file to preserve compatibility with both Rails 2 and Rails 3 versions.

Deprecated Methods

There are a bunch of Rails 2 methods that are going to be deprecated in Rails 3. Here's a not-complete list:

  • ActiveRecord::Base.add_to_base(msg) has been deprecated. Use ActiveRecord::Base.add(msg) insted. This method already works in Rails 2.

Rails 3 upgrade

The steps above are not enough to make your project a real Rails 3 application, they are just a small subset of changes you should apply. The big difference which is also the main advantage compared with all the other changes, is that you don't have to wait for Rails 3 to adapt the code. The more steps you complete now, the less you will have to change while upgrading to Rails 3.