A few days ago, frestyl launched an invisible, but nonetheless enormous upgrade. We are now running on Rails 3. It was a time-consuming process, but the payoff is well worth the work. Web applications like frestyl come in every shape and color imaginable, and Rails 3 introduces a new standard for developing them elegantly and efficiently. Rails has a large and active community, and Rails 3 was created with this in mind. It makes it easy and seamless to incorporate the best tools available through a wide network of developers. We are now able to take full advantage of everything both Rails 3 and this community has to offer. This will allow us to rapidly develop new features and continue to make innovative improvements to frestyl.

From the beginning, frestyl has had a philosophy of staying on the cutting edge. We are constantly thinking of ways in which we can utilize new technology to enhance the experience, and development of frestyl. The upgrade to Rails 3 is no different. Of course, adapting to new technology brings unique challenges, and we always try to contribute our knowledge and insights back to the community. As developers, we believe this helps to foster a thriving community, and actively drives improvements to our code as well of others’.

The rest of this post will be a step-by-step guide about our practical experiences in upgrading our production application to Rails 3. We hope that this will save you all a lot of Googling and hair-pulling-out if you are about to embark on the same journey.

First Things First: Cover Your Tests
Rails 3 introduced a lot of changes that greatly diverged from the previous ways of doing things. These changes were necessary to achieve one of the main design goals of separating the public API from the internal workings of the framework itself. As such, we felt the best way to be certain that our code properly conformed to the new API and did not rely on deprecated code was to accurately test and fully cover the entire application. While this is certainly easier said than done, it highlights why thorough testing is essential. While we hope that when Rails 4 is released in the future it does not introduce such sweeping changes, we will nonetheless be prepared with fully covered tests.

Don’t Try To Be A Hero: Use the Rails Upgrade Plugin
On GitHub: https://github.com/rails/rails_upgrade

The Rails Upgrade Plugin runs “checks on your Rails 2.x/3.x to check for obvious upgrade points on the path to 3.0.” It generates a list of deprecations and changes as well as provides information on how to fix them. It also creates backup files of those that would be overwritten by generating a new Rails 3 app. Once we had full test coverage, we installed the plugin and began the upgrade process.

Our Roadmap
What follows is, roughly, the route we planned to take us to Rails 3, and it worked well for us. For most steps, there is no easy way to do it rather than biting the bullet and fixing a lot of code. It helps if your text editor has a good regexp find-and-replace. However, where applicable, we’ve supplied some notes of certain aspects that were particularly difficult in the hopes that you will save some research time when you decide to make the switch.

1. Read this from start to finish: Ruby on Rails 3.0 Release Notes
2. Capture the output of rake routes to a file
3. Install the Rails 3 plugin and fix deprecated code
4. Generate rails 3 app over existing app
5. Upgrade configuration
6. Adjust initializer scripts
7. Fix code and install gem updates until the environment properly loads
8. Re-write the routes file
9. Change mailer code for compatibility with the new API
10. Fix test configuration for RSpec 2 compatibility until the tests run again
11. Watch all of your tests fail and silently weep
12. Start fixing everything until full test coverage is achieved
13. Clean up failing cucumber features, and do human testing
14. Deploy!

Capture Your Routes
Before you do anything that will prevent your new Rails 3 environment from loading correctly, run rake routes and capture the output to a file. You will need this to compare the new routes generated by Rails 3 after you have installed the gems and updated your configuration.

Upgrading the Application Configuration
The configuration for a Rails 3 app no longer lives in config/environment.rb. It has been moved to config/application.rb. In most cases you can simply move your Rails 2 configuration options into the Application < Rails::Application definition.

Protips:
• If you rely on code in lib, make sure you add #{Rails.root}/lib to config.autoload_paths as Rails 3 no longer auto-loads this directory by default.

• Move filter_parameter_logging :param out of ApplicationController and set config.filter_parameters << :param

• If you use another ORM such as Mongoid alongside ActiveRecord, add this to the configuration:
config.generators do |g|
  g.orm :active_record
end
This will ensure that rails g model my_model generates ActiveRecord models and not Mongoid documents.

config.action_view.cache_template_loading no longer exists and defaults to the value of config.cache_classes.

Adjust Initializer Scripts
config/initializers/cookie_verification_secret.rb is now config/initializers/secret_token.rb in Rails 3

• Change ActionController::Base.cookie_verifier_secret = 'secret' to YourApp::Application.config.secret_token = 'secret'

• In config/initializers/session_store.rb, remove:
ActionController::Base.session = {
  :key => 'session_key',
  :secret => 'secret'
}

and add the :key option to the session store config: YourApp::Application.config.session_store :active_record_store, :key => 'session_key'

• Remove config/preinitializer.rb
ActionController::Dispatch.middleware is now YourApp::Application.config.middleware
ActionController::ParamsParser is now ActionDispatch::ParamsParser

Update Gems and Get the Environment to Load
Now it’s time to install the Rails 3 gems. At this point, your configuration should be correct, but you still have to worry about incompatibility with your gems. Many older versions of gems do not support Rails 3. There is no easy way to solve this problem other than to do your research and figure which gems have either been patched for Rails 3, or have stable Rails 3 repository branches. In particular, if you use AuthLogic, you will need to install at lease version 2.1.6. And if you use Thinking Sphinx for search, you will need to upgrade to at least version 2.0.0.

Protips:
• Thinking Sphinx > 2.0.0 no longer requires you to have lib/tasks/thinking_sphinx.rake inside your app directory. You can remove it.

• As of this writing, AuthLogic 2.1.6 is still not 100% Rails 3 compatible and causes deprecation warnings. To suppress these, you can add this code to your User model, or whatever model acts_as_authentic:

def reset_perishable_token!
  reset_perishable_token
  save_without_session_maintenance(:validate => false)
end

Once we had solved issues with gem incompatibility, we made repeated attempts to successfully run rails console. By now, your configuration should be sane enough where the exception backtrace actually tells you something useful to fix. Keep fixing things until it works. Then reward yourself with ice cream.

Re-Write the Routes File
The routing API in Rails 3 has completely changed. The full guide can be found here: Rails Routing from the Outside In. Thankfully, the Rails Upgrade Plugin does an excellent job of converting your existing Rails 2.x routes file to the Rails 3 API.

In order to ensure that your routes are still correct, you’ll need the output file to which you captured your Rails 2.x routes. Then, use the Rails Upgrade Plugin to generate the new routes file. Re-run “rake routes” with the new routes file now that Rails 3 is installed, and capture the output to a new file. Alongside routing tests/specs, you may want to manually compare each individual route and make changes to the routes file until the route set is exactly the same as your original Rails 2.x routes.

Upgrade to the New Mailer API
Rails 3 introduces a new way of setting up messages. With the old API, a method in an ActionMailer subclass would have looked like this:

ExampleMailer < ActionMailer::Base
  ...
  def message(user)
    recipients [user.email],
    from "no-reply@example.com"
    sent_on Time.now
    subject "Example"
    body { :var => "value" }
  end
  ...

You would have delivered this message with ExampleMailer.deliver_message(user). The new API makes setting up messages cleaner, as it allows you to setup defaults, and does away with the body hash in favor of instance variables. With the new API, the above example translates into this:

ExampleMailer < ActionMailer::Base
  default :from => "no-reply@frestyl.com"
  ...
  def message(user)
    @var = "value"
    mail({
      :to => user.email,
      :subject => "Example"
    })
  end
  ...
end

Deliver the message with ExampleMailer.message(args).deliver.

You Are Almost There… Sort of
Now that you are certain that your routes are correct and you’ve fixed deprecated code, it’s time to do a little bit of clean-up. You are now beyond the point where you need the Rails Upgrade Plugin, so remove it along with any other dependencies that are not required by any other installed gems. You can also remove many of the files in the scripts directory, as they have been replaced by the new rails command:
script/about
script/console
script/dbconsole
script/destroy
script/generate
script/plugin
script/runner
script/server
script/performance/benchmarker
script/performance/profiler

Upgrading to RSpec 2
You can skip this part if you do not test with RSpec, but you will almost certainly still need to make many more fixes until your test suite once again runs cleanly.

If you use RSpec, Rails 3 requires RSpec 2. There is no easy way to upgrade other than continually trying to run the tests until it works, in much the same way as getting the environment to load above. Of course, before you do this you’ll need to make several RSpec configurations. In addition, an approach we found helpful was to first remove any RSpec 1.x extensions or add-ons before upgrading. For example, prior to RSpec 2 we were using the excellent RSpec on Rails Matchers, but unfortunately this library is incompatible with RSpec 2.

Fix the RSpec Configuration
Spec::Runner.configure becomes RSpec.configure
• If you use AuthLogic, you’ll need to set config.include AuthLogic::TestCase
• If you are using RSpec’s built-in mocking framework, you’ll need to set config.mock_with :rspec

Keep View specs Compatible
If you have tons of view specs like us, it is overkill to try and rewrite them for the new non-backward compatible API. Instead, we installed the rspec2-rails-views-matchers gem.

Fix Deprecated/Missing Methods
Most of what we needed to change is outlined here, in the hopes that it will save you a few hours of googling. You may also find this link helpful, which provides a cheat sheet for many of the changes from RSepc 1.x to RSpec 2: http://snyderscribbles.blogspot.com/2011/01/rspec-2-changes-from-rspec-1.html

• The be_close(value, threshold) matcher has changed to be_within(threshold).of(value)

Controller specs:
• Accessing assigns has changed from assigns[:var] to assigns(:var)
• Setting assigns has changed from assigns[:var] = var to assign(:var, var)
response.layout.should == ... becomes reponse.should render_template(...). If you want to test that a template renders with a certain layout, you must test that both the template and the layout are rendered.

View specs:
response.body.should ... becomes rendered.should ...
template.should becomes view.should
• We needed to install the webrat gem, and change the have_tag matcher to have_selector

• We created our own fork of shoulda-matchers to replace some of the missing matchers. You may also find it useful: https://github.com/frestyl/shoulda-matchers.

Rejoice
Your app runs, and your test suite passes. And the best part: you’re now running on Rails 3! Have some ice cream.

Before your deploy, it goes without saying that you should do a healthy round of actually human testing, in addition to ensuring that your cucumber features still behave properly. But you have gotten most of the dirty work done, and you are now free to take advantage of all the great new features of Rails 3.

Love,

:: npj & jodosha

Advertisements