Darwinweb

Getting Up To Speed With Rails 3

January 22, 2010     

I’m proud to announce that darwinweb.net is now running Rails 3.0-pre. I undertook the project as part of Bugmash last weekend, but it turned out to be a very deep rabbit hole; a two-day bugmash was too short to really dig into the meat of Rails 3 which is in ample supply.

The scope of changes to Rails is astonishing. There’s a lot of new blood contributing and brilliant ideas are popping left and right. The result is that Rails 3 represents a major maturation inflection point. It’s graduating from the scrappy, opinionated upstart to a very modular and full-featured framework containing what may once have been considered a disturbing amount of enterprise-readiness. It’s bittersweet and perhaps a bit ironic that the internals have acquired so many moving parts, but as a professional Rails developer the new flexibility and modularity is far too awesome to denounce in any way.

Rails 3 is a hardcore developer’s release. It should drive a final nail into the coffin of the noob framework meme—the ridiculous stereotype DHH unwittingly spawned with his infamous 15-minute blog scaffolding demo. Rails 3 is not about bells and whistles; its meaty meta changes will empower the next generation of framework designers and inspire them to build on the Rails and Ruby ecosystem.

Now that I’m done gushing…

This article is a brief rundown of the steps I took to darwinweb (which is a custom Rails app dating back to Rails 0.13). At the end I’ll provide a list of resources that represents the best information about upgrading to Rails 3 at this moment (and I’ll keep it up to date until the release, so send me any new links you have). Let’s begin.

Using git to upgrade an existing app to Rails 3

Rails comes with some rake tasks to perform version upgrades. I’ve used them before for point-release upgrades. However Rails isn’t a Microsoft OS with a several billion dollars of long-term support contracts pouring into the core team’s coffers to fund extensive upgrade tooling and testing. I’m more confident with a fresh start.

What I did this time was to spin up a new app according to Yehuda’s instructions, and then copy the .git directory over

mv darwinweb.net darwinweb.net.old
~/rails3/railties/bin/rails darwinweb.net
cp -pr darwinweb.net.old/.git darwinweb.net/

At this point you have a stock Rails 3 app and you can use git to inspect any of the differences to your current app. From there you can use git-checkout to restore files that don’t need to be updated and git-add (perhaps with the -i flag) to commit the necessary upgrade changes.

Git’s ability to visualize this state and keep partial changes in the index without committing is a dream come true. If you pipe the diff to TextMate you can very starkly see the new Rails 3 stuff because it will show up in green, while all the red is your existing app stuff.

It took me about an hour to work through this stuff after which I had about a dozen individual commits updating various details. The biggest change was the addition of a module name for the application, but there were numerous other subtle path changes and such. I won’t outline them here because I might miss some cases for older upgrades, or there might be more changes before release. The important thing is that with this technique I have absolute confidence that I am not missing any parts of the upgrade.

Bundler

Bundler is one of the most pragmatic enhancements in Rails 3. It is definitely a big shift and has a lot of facets to wrap your head around that may seem unnecessarily complex to small app developers, but for anyone who has suffered some of the pain points with RubyGems in large apps, Bundler bestows an appropriately comforting feeling.

I spent a good amount of time playing with Bundler settings, especially in the context of deployment. A lot of the information out there concerns using Bundler under Rails 2.3.x, which is mercifully no longer relevant. After all was said and done, Yehuda’s Using the New Gem Bundler Today contained the essential information I needed.

Once you set up your manifest (Gemfile) and run vendor them using gem bundle, the main thing to realize is that the gems are only added to the path, they aren’t actually included automatically. If you want the old-style behavior you can simply call Bundler.require_env, but I sort of like being able to require the gems explicitly in the relevant locations.

Yehuda’s documentation is pretty good, but there is one conspicuously missing detail about the latest version.

Bundled gems now have the full gem directory structure

So everywhere where someone mentions a directory of the form /vendor/gems/*, generally it will end up as /vendor/gems/ruby/1.8/* on the latest version. This has some implications to things like .gitignore.

Digging deeper there was another place I differed from the recommendation.

How .gitignore, Bundler and Capistrano interact

One of the core goals of Capistrano is that you can commit all the gems to the repo and be able to deploy without pulling anything from remote servers. Bundler is about 95% there, but there is a problem if you have git repos in your Gemfile (such as you currently do for arel and rack in edge rails). The repositories are checked out inside vendor/gems/ruby/1.8/directories. These are full git repositories which can be efficiently updated by bundler via git-pull. However, having nested git directories leads to automatic submodule creation, which defeats the purpose of caching all the gems, plus I don’t like the complexity that submodules add in general.

One possibility is to completely remove vendor/gems from the repo which is nice because it leads to a smaller repo, but also means that gem bundle needs to rebuild the bundle on every single deploy.

To solve that problem I decided to go with the old standby of Capistrano shared directories.

Capistrano Recipe

I have a pretty vanilla Capistrano recipe that uses rsync_with_remote_cache to allow deployment from my local repo without needing to publish to a accessible repo on the web. To get Bundler working first I manually create the shared directory:

cap invoke COMMAND='mkdir /path_to_app_dir/shared/gems'

Then in the deploy.rb recipe I symlink the bundle directory and then run gem bundle which performs any necessary updates:

namespace :deploy do
  task :symlink_gems do
    run "ln -nfs #{shared_path}/gems #{latest_release}/vendor/gems"
  end

  task :bundle_gems do
    run "cd #{current_path} && gem bundle"
  end
end

after "deploy:symlink", "deploy:symlink_gems"
after "deploy:symlink", "deploy:bundle_gems"

Passenger Working Great with Rails 3.0

Rails 3 is working out of the box on Passenger 2.2.9 which I am running on Nginx under Ubuntu on a prgmr VPS. Monkey patching and/or the preinitializer.rb hack you will see around are definitely no longer necessary. I had one gotcha which was that I needed Rack 1.1.0 installed on the system. When I had Rack 1.0.1 installed Passenger threw a Rack-related error, which I thought was strange considering that Rack is supposed to be bundled.

Application Changes Required

Darwinweb is a very simple blogging app, so there’s not much application code to be upgraded and are certainly among the more obvious upgrade changes that will be announced in the CHANGELOG. Nevertheless, here they are for posterity:

Routes

Routes are using a completely new DSL. Updating took about 10 minutes to update this with the help of Rizwan’s blog entry on the topic.

FactoryGirl and Shoulda

There is an official FactoryGirl Rails 3 branch, but I found that the current versions of FactoryGirl and Shoulda were already mostly working. The main problem was auto-discovery of factories in test/factories. I didn’t like the official fix because you have to work around Bundler. Instead I the following to my test_helper.rb after the environment is loaded.

require 'shoulda'
require 'factory_girl'
Factory.definition_file_paths = [ File.join(Rails.root, 'test', 'factories') ]
Factory.find_definitions

render :partial syntax

I had some forms that used :object => foo syntax, which is no longer supported. The good news is that the same syntax now passes arbitrary locals which is nothing but better.

ActionMailer now more controller-like

Setting instance variables rather than passing a hash. Nice symmetry to ActionController, and the deprecation warning made it obvious.

Change to scope from named_scope

The ActiveRecord stuff is looking awesome. I had a few syntax errors crop up as commits from Pratik were flying through. Those semantics seem to be in a bit of flux still, but already ActiveRecord querying seems much more powerful than what was available in 2.3.×.

HTML auto-escaping

Koz’ Rails XSS plugin is now built-in to Rails 3. This change will probably be one of the most tedious for large apps, but I’ve put a lot of time into sanitization over the years, and I can say unequivocally that the pain is necessary and worth it.

Most likely you will need to spend hours going over your templates and helpers with a fine-tooth come to set up the HTML safety. Tedious, but no simpler solution works in the general case. Filtering input (eg. xss_terminate) is convenient, but it botches user intentionality and is at best only a good solution for a minority of apps.

Updating a generator

I updated the tm_syntax_highlighting plugin, and got a little taste of Rails 3 generators. Suffice it to say that this is a topic unto itself that deserves many deep blog posts.

Rails 3 Resources & Links

These are a choice selection of articles that I found to be most useful for getting up to speed with the current state of Rails 3. They are ordered non-arbitrarily:

Loughlin mcsweeney says…
April 26, 2010 at 6:42AM

Excellent article, great approach for a simple upgrade from 2.3 to 3

Felipe says…
March 23, 2010 at 10:18AM

I think you should replace “gem bundle” by “bundle install”.