I have created a few rails 3 sites, but i have never before ported an existing rails2 application to rails3.

I will describe the problems i encountered.

First off there were some very good resources (step-by-step descriptions) to guide me through it:

The conversion ran pretty smoothly, but I will describe the things i bumped into that were less obvious.

ActiveSupport::Callbacks

Before we wrote:

  module Workflow
  class Base
    define_callbacks :before_something, :after_something

    def some_method
      run_callbacks :before_something
      puts "some method"
      run_callbacks :after_something
    end
  end

  class SpecificWorkflow < Base
    before_something :do_something

    def do_something
      puts "something"
    end
  end

Now, this has changed. Into the following:

  module Workflow
  class Base
    define_callbacks :something

    def some_method
      run_callbacks :something do
        puts "some method"
      end
    end
  end

  class SpecificWorkflow < Base
    set_callback :something, :before, :do_something

    def do_something
      puts "something"
    end
  end

Which is pretty nice. It has the disadvantage we can only use :before and :after callbacks anymore, but that was easily solved.

ActionController::ParamsParser

We hooked into the Rack middleware before the ActionController::ParamsParser to make sure we return Bad Request when the parsing fails.

This middleware has been renamed to ActionDispatch::ParamsParser. Easy fix :)

safe_helper removed?

We prepared our Rails 2 project a long time ago, and used the rails_xss plugin. We used the safe_helper all over our helper-methods, but apparently this is not supported in Rails 3. Bummer.

So, before

  def some_helper
    "<strong>something</strong>"
  end
  safe_helper :some_helper

After:

  def some_helper
    "<strong>something</strong>".html_safe
  end

A bit annoying to do, a bit unfortunate, because we thought we were preparing ourselves for a smoother upgrade. Not entirely so.

The routes!

Only after those steps did my rails get far enough to discover my routes. I did not change anything about the routes, just placed inside the correct block. I deleted my index.html and put back my original application.html.haml and the first view was working.

The missing to_key

The to_key was missing inside the user_session model. I described that solution already in this blogpost. So i just needed to fix that.

Replacing ActionController::RecordIdentifier.singular_class_name

We used ActionController::RecordIdentifier.singular_class_name, in rails3 this is replaced by ActionController::RecordIdentifier.dom_class.

Missing translations

A feature we used a lot was I18n.t('.filter') and this would look for operators.filter.filter because the line was inside a partial _shared/_filter.html.haml and called from the operators/index.html.haml.

Now this does not work anymore, and now instead I18n looks for _shared.filter.filter.

I did not find an easy way to solve this, just moved some translations. Luckily we did not override the translations of a partial based on the context where it was rendered yet. Does anybody has any tips on that?

Upgrading javascript helpers

In rails3 unobtrusive is the way to go.

So, a remote_form_for like

- form_remote_tag :url => update_url do

becomes

- form_tag :url => update_url, :remote => true do

Which seems easy enough. It does get more complicated when the :update tag is used.
For example:

- remote_form_for post, :update => {:success => "result_#{id}"} do |form|

becomes

- remote_form_for post, :remote => true, :html => {:class => 'remote-post-form', :'data-update' => "#result_#{id}"}  do |form|

The :update is no longer supported as before, and we have to perform some glueing ourselves. So i replaced the :update by data-update. And then inside javascript (e.g. application.js) we can do the following:

jQuery(function($) {
  $(".remote-post-form")
    .bind("ajax:success", function(data, status, xhr) {
      var update_selector = $(this).attr('data-update');
      $(update_selector).html(status);
    });
});

If you need more elaborate example, check this question on stackoverflow.

master_helper_module

This has been replaced by _helpers.

active_layout

The active_layout method, of a controller, no longer exists inside rails3.

To replace it you need to use two methods:

  • action_has_layout? returns true if a layout exists.
  • To get the layout-name, i have no better solution then calling the private method _layout. This can be done, as known, by doing controller.send(:_layout).

Upgrade rspec

I had to upgrade Rspec 1 to v2. First off in the Gemfile i added the correct version.
I removed the rspec.rake file. I ran

rails g rspec:install

But still all rake spec commands were missing.
The fix was simple. In the Gemfile make sure the rspec-rails is also usable from development mode:

group :development, :test do
  gem 'rspec-rails'
end

Now on to fixing the tests!

Fixing the tests

In the upgrade from rspec1 to rspec2 a lot of things have changed.

What i encountered:

  • replace all Spec:: by RSpec::
  • controller_name 'users' no longer exists, write a surrounding describe UsersController do instead (that is also clearer imho)

Extending Rspec::Rails::ExampleGroup::ControllerExampleGroup

Before, in rspec1, ControllerExampleGroup was a class and you could add code to it.
So for instance, we had a file like this in our support folder:

module RSpec
  module Rails
    module Example
      class ControllerExampleGroup

        let(:the_account                  ) { Factory(:account) }
        let(:the_user                     ) { Factory(:user)}

      end
    end
  end
end

A bit larger, but you get the picture: this allowed us to define a set of shared let definitions.
In rspec-2 this is no longer possible this way, because ControllerExampleGroup is now a module.

I handled that like this:

module Lets

  def self.included(base)
    base.let(:the_account                  ) { Factory(:account) }
    base.let(:the_user                     ) { Factory(:user)}
  end

end

and inside my spec_helper this file is automatically required (since it is stored in the support folder) and i just add

  config.include(Lets)

Rspec: replace config.extend

I also noticed that using config.extend(ModuleName) dit not work anymore.

Instead i had to write (on the same place, inside the configure block) :

Rspec.configure do |config|
  ..
  include ModuleName
  ..
end

and that worked for me.

More?

Do you have more hints to share? What bumps did you find and how did you overcome them?