Getting Up To Speed With Rails 3
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:
- The Path to Rails 3: Introduction – Jeremy McAnally lays down an epic intro to the big pieces of Rails 3.
- Spinning up a New Rails App – Yehuda Katz gets you started.
- Active Record Query Interface 3.0 Pratik Naik blows your mind.
- Rails and Merb Merge: Rails Core Yehuda Katz explain some of the deep and far-reaching changes in Rails core architecture. I really could have used this article a week ago.
- Using the New Gem Bundler Today – Yehuda Katz documents both the design criteria and API of Bundler rather comprehensively, remaining relatively complete 2 months later (could use an update documenting the 0.8.1 changes).
- Rails3-Ready Plugins & Gems – It’s been years since I even looked at the Rails Wiki, but this page is up-to-date and useful at the moment.
- Revamped Routes in Rails 3 – Rizwan Reza gives you the fast and yet reasonably complete rundown of the new routing DSL.
- The Rails 3 Router, Rack it Up – A few days later Yehuda Katz steals his thunder and fills in some of the gaps. Hey, I could link to every post Yehuda’s written this year, so I’ll stop here and let you add him to your RSS.
- Customizing Generators in Rails 3 – Paul Barry shows some of the flexibility built into Rails 3 generators purely from the configuration side.
- Making Generators for Rails 3 with Thor – David Trasbo scratches a bit deeper into the components behind Rails 3 generators. This was very helpful for getting my footing in the source.
- Passenger 2.2.9 Release Notes – The canonical source for the Rails 3 compatible release of Passenger.
- The Path to Rails 3: Approaching the upgrade – Jeremy McAnally with part 2 in the series. Similar to this article.
- Upgrading an Application to Rails 3 Part 1 – Another post by David Trasbo in the same spirit as the one you are reading.
- Notes from the field upgrading to Rails 3 – Yet more upgrade notes, this time from the mysterious Sam, some useful tidbits in here.
- Rails 3 Reading Material – Finally, Maxim Chernyak has a list of links by topic and date if you’re hungry for more.
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”.