Notes from the field upgrading to Rails 3

note I will be testing out the Rail3 upgrader and post a follow up article.

During the Rails 3 bug mash I decided to see what happens when I migrate a Rails 2.3.5 app to Rails 3.

Much has changed, the upgrade path is not trivial. Nonetheless, this site is now running Rails 3 with nginx and Ruby 1.9.2 head. So you can feel it in action.

Learn to love bundler

The proper way to manage dependencies in Rails 3 is using a new gem called bundler. This gem gives the gem command an extra command namely: gem bundle

When you run gem bundle it will determine all the correct dependencies for the files specified in a file called Gemfile

This is the Gemfile (which is in the Rails.root directory) for this site uses:

gem "rails", :git => "git://github.com/rails/rails.git"
gem "arel", :git => "git://github.com/rails/arel.git"
gem "authlogic", :git => 'git://github.com/binarylogic/authlogic.git'
gem "ruby-openid", :require_as => "openid"
gem "uuidtools"
gem "hpricot"
gem "bluecloth"
gem "diff-lcs", :require_as => "diff/lcs"
gem "liquid"
gem "rdiscount"
gem 'sanitize'
gem 'will_paginate'
gem 'haml'
gem 'mysql'
gem 'memcached'

Yehuda Katz has written extensively about the bundler. In a nutshell, dependencies using the old config.gem way of doing things in Rails 2 is fundamentally broken. Bundler fixes this. It creates a directory where all the dependencies exist. Meaning unlike Rails 2, each application in Rails 3 is meant to have a full copy of all the gems it depends on.

After this Gemfile is created you can run gem bundle to pull in all your dependencies. You can use this trick with other ruby apps as well, bundler is totally reusable. Bundler is how you vendor Rails and how you grab all your plugins.

So, to summarize step one: grab all your config.gem lines in your environment.rb file, and place them in your Gemfile, remove the config. and replace :lib with :require_as, require rails from github at the top.

The voodoo boot process of Rails 2 has been replaced

Take a minute and have a look at your Rails 2 boot.rb file. Its long and complicated.

Compare it with this sites boot.rb file:

# config/boot.rb
require File.expand_path('../../vendor/gems/environment', __FILE__)
# you can even require portions of rails instead of the whole kaboodle 
require 'rails/all'
# since there is no config.gem anymore, require stuff you need to star your app here.
require 'authlogic'
require 'will_paginate'
require 'rdiscount'
require 'uuidtools'
require 'openid'

You also need a new environment.rb file, the old one contained lots of configuration data, instead environment.rb in rails 3 only contains a simple step:

require File.expand_path('../application', __FILE__)
CommunityTracker::Application.initialize!

You will need to set up an application

Gone are the days of Rails::Initializer and Rails.boot Rails 3 is serious about the move to rack, the best practice is to have an application.rb file that defines what it is your application does. This is the current sites one:

# config/application.rb
module CommunityTracker
  class Application < Rails::Application

    # Specify gems that this application depends on and have them installed with rake gems:install
    config.time_zone = 'UTC'
    config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {:address => "localhost", :port => 25, :domain => "localhost"}

    config.middleware.use "RequestCache"
  end
end

if defined?(OpenID)
  OpenID::Util.logger = RAILS_DEFAULT_LOGGER
end

ActionView::Base.field_error_proc = Proc.new{|html_tag, instance| %(<span class="field-with-errors">#{html_tag}</span>)}
require "haml"
require "haml/template"
Haml::Template.options[:format] = :html4
Haml::Template.options[:escape_html] = false

Most of your old code from environment.rb can stay here but you will have to get rid of all those config.gem lines and use bundler for that.

It’s a Rack application sir

You need a new config.ru file in your Rails.root

# config.ru
require ::File.expand_path('../config/environment',  __FILE__)
run CommunityTracker::Application.instance

This is the rack up file for your application, Rails 3 takes rack very seriously.

Your config files are broken

All the files in config/environments need some special handling:

# production.rb 
CommunityTracker::Application.configure do
  config.action_mailer.default_url_options = {:host => 'community-tracker.com'}
  config.cache_classes = true
  config.action_controller.consider_all_requests_local = false
  config.action_controller.perform_caching             = true
  config.action_view.cache_template_loading            = true
end

Note the new block.

No more RAILS_ENV, RAILS_ROOT etc.

If you want to avoid a ton of pesky warnings, better move to using Rails.root and Rails.env today, rails 2.3.5 supports this syntax, the old RAILS_ENV is deprecated and it nags you to change it.

XSS protection is everywhere

Rails 3 tries to protect you from all sort of nasty cross site scripting. This protection is baked in pretty deep. Gone are the days you have to remember to escape a string using the h method. Instead Rails 3 assumes all the strings are unsafe, the various view helpers are hooked up to perform escaping for you.

Every string knows if it is safe for html rendering or not. (from active support)

class String
  attr_accessor :_rails_html_safe
  alias html_safe? _rails_html_safe

  def html_safe!
    @_rails_html_safe = true
    self
  end

  def html_safe
    dup.html_safe!
  end
#...
end

So, if for example you would like to link to a bold string use html_safe!

link_to "<b>google</b>", "http://google.com" #results in an escaped &lt;b&gt; 
# this works
link_to "<b>google</b>".html_safe!, "http://google.com"

This issue will affect any large scale Rails app.

Rail 3 is much more strict.

In Rails 2 you could get away with having your helper functions defined in the wrong file, this is no longer the case in Rails 3. This is a good thing, it forced me to better organize my helpers.

Ruby 1.9.2 head is surprisingly stable and compatible

You can see the list of gems I'm using, its quite extensive. Most of the gems I tried work in 1.9.2 and the ones that do not have usually not been updated for years. 2010 may be the year people really start moving to 1.9.

Weird issues

Obviously not everything is perfect, Rails 3 is pre-release software which is not meant to be running in production. I noticed a few notable strange issues:

  1. When requiring gems in a lib sometimes the whole request just crashes, I have not figured out exactly how or why this happens.
  2. I can not run my application in development mode, I use rack middleware, and something about my middleware is wrong and Rails refuses to reload it in development mode.
  3. No rspec, rspec does not work on Rails 3, the rumor is that it will be supported in the rspec 2 time frame.
  4. The logger is a bit rough, the logging in rails 2 seemed a bit more consistent.
  5. This issue is likely to affect anyone using the open-id gem.

Posted by sam on January 20, 2010

4 comments

Druiden Druiden says:
January 23, 2010

Thanks for the best Rails 2 to Rails 3 explanation so far!

One question that I'm still wrestling with though is how to use the gem bundler when deploying.

With config.gem I used to bundle (unpack) some of the gems (typically the ones not requiring native extensions) in my git repo and the rest I would install with sudo gem install on the server manually.

With “gem bundle” it seems every dependency is bundled into the vendor/gems mega-directory-structure with loads of subdirectories and behind the scenes magic I don’t fully comprehend.

What should I version control and what not? What should be in the .gitignore? Should I somehow gem bundle after cap deploying?

I never really understood the issue with the old way of bundling and I have never heard from anyone who ran into any problems with it but I understand there CAN be problems. To me gem bundle seems a lot more complicated than the old system and when fiddling with Rails 3 test-applications I've been tempted to just leverage the backwards compatibility with config.gem and vendor/rails which seemed simple and easy to grasp and worked well in all the Rails applications I've ever seen.

dharmarth dharmarth says:
January 23, 2010

Thanks for the informative and helpful post.

Few days back I have also experienced reloading problem with Rails (>=2.3.4) development environment. For me, it was non-compliance with Rack specification, specifically for Response Body (If the Body responds to close, it will be called after iteration).

sam sam says:
January 25, 2010

@dharmarth

In theory the bug with Rack is caused by https://rails.lighthouseapp.com/projects/8994/tickets/2873 which should be fixed in Rails 3 shortly.

@Druiden

Make sure you have a read of wycats’s article at:http://yehudakatz.com/2009/11/03/using-the-new-gem-bundler-today/ . You can store some of your unpackaged gems in git if you wish and specs so you freeze versions. You get the same level of flexibility with bundler as you did config.gem

raimo raimo says:
February 09, 2012

I came up with this kind of solution when going through all the views in a Rails 2 application. You might get some ideas on how to proceed with Rails 3 upgrade regarding XSS issues:

http://developer.uservoice.com/entries/upgrading-to-rails-3-printing-escaped-strings

Your Comment

Please login to leave a comment