Upgrading to Rails 3.0.0.rc

The release candidate of Rails 3 released yesterday contains quite a few surprises for the careless updater from earlier Rails 3 betas (me totally included). As usual, a proper test suite will save your bacon.

Here is the list of pitfalls I encountered, with an advised solution/workaround where available.

Rails 3.0.0.rc requires Bundler 1.0.0.rc

Before you start getting weird errors, do yourself a favor and manually install the Bundler gem or the Rails gem (instead of going through Bundler itself trying to resolve this meta-version-inter-dependency-hell).

$ gem install bundler --pre

or install the Rails pre-release manually, since it depends on the Bundler pre-release

$ gem install rails --pre

This will bite you on your staging/production servers at the very latest. Bundler 1.0.0.rc will also completely make over with your existing Gemfile.lock, so don't be surprised to find a completely new (and excitingly readable) format after you bundle install.

On a related note, bundle install --relock is gone. I am under the impression that bundle install (without arguments) will now just default to relocking the bundle if a Gemfile.lock is detected.

Deprecation Warnings

Between Beta 4 and the Release Candidate, the Core Team actually snuck in a few extra deprecation warnings that will affect those coming from earlier betas. Newly generated applications (by way of rails new <appname>) will be generated with the new syntax.

First of all, you will get "Calling a method in Rails::Application is deprecated" for any and all rake tasks. The fix goes into your Rakefile and is very simple. (Please substitute YourAppName for the actual name of your application.)

# Old
Rails::Application.load_tasks

# New
YourAppName::Application.load_tasks

Additionally, running your tests may reveal another deprecation notice to the effect of "You are using the old router DSL which will be removed in Rails 3.1", with an unhelpful pointer to deprecated_wrapper.rb. This time, the fix goes into your config/routes.rb:

# Old
YourAppName::Application.routes.draw do |map|
  # ..
end

# New
YourAppName::Application.routes.draw do
  # ..
end

Spot the difference? And the notice is gone.

Additionally, there's an environment-specific way to declare your desire to receive deprecation warnings (or not). These are the recommended settings:

# config/environments/test.rb
config.active_support.deprecation = :stderr

# config/environments/development.rb
config.active_support.deprecation = :log

Ironically enough, even that will make another warning go away.

Content from lib/ isn't auto-loaded anymore

If you rely on libraries in your application's lib/ sub-directory being auto-loaded when you start using their class names in your code, you need to add a passage to your application's configuration:

# config/application.rb
module YourAppName
  class Application
    config.autoload_paths += %W(#{config.root}/lib)
    # ..
  end
end

ActiveRecord::Base#class_name is gone

In a cruft cleaning attempt, ActiveRecord::Base#class_name has been removed. You can use ActiveRecord::Base#to_s as a likely replacement. (Worked in my simple use cases.) Update: Jeremy McAnally suggested using ActiveRecord::Base#model_name as the replacement. Thanks!

I18n: Changes to interpolation

While we're waiting for the official rails-i18n repository to update most of their language files (or fork away and fix it yourself), please be aware that you will see even more deprecation warnings regarding the interpolation of variables within the translation strings.

# Old
other: 'etwa {{count}} Stunden'

# New
other: 'etwa %{count} Stunden'

So it's just like regular Ruby string interpolation.

The returning of the tap

Object#returning has been present in ActiveSupport for quite a while and supported concise constructs where you're manipulating an object multiple times before finally returning it from the method. Well, Object#returning is dead, long live Object#tap, which has a slightly different syntax.

# Old
returning([]) do |output|
  # ..
end.join("\n")

# New
[].tap do |output|
  # ..
end

Basically, it's a little cleaner and potentially less confusing with a regular return statement.

Changes to ActiveRecord::Base#update_attribute

Last, but by no means least, the underlying implementation of ActiveRecord::Base#update_attribute was changed drastically. On the surface, it's for the better. But upgrading applications that depend on the old behavior is going to be a major headache and source for very subtle bugs.

It will only save the attribute it has been asked to save and not all dirty attributes

If you'd been using update_attribute as a sort of "final call" to changing a potentially protected attribute and then making that save the whole record (with additional dirty fields), this will now only update the field passed to update_attribute itself and will not go through a regular save operation. The underlying implementation actually makes a direct SQL call to accomplish this.

It does not invoke callbacks

This will get you if you relied on the fact that callbacks indeed fired with the use of update_attribute. One example would be the usage of the excellent friendly_id plugin that takes care of auto-generating permalink slugs for your models and also has a way to keep those slugs in sync with the fields they're derived from. If, for some reason, you were to use update_attribute on said field, the callback to update the slug will not fire and thus the permalink will be out-of-date.

Keep your eyes open and let me know if you run into other issues or gotchas worth publishing.

PS: Snowman!