Potential gotcha with test setup in Rails 2.0

Wednesday, December 05

UPDATE 2: This is now fixed in Rails trunk.

UPDATE 1: Josh Peek has already created a patch that prevents this gotcha. Read the patch and give it a +1 on the Rails Trac if you’re so inclined: http://dev.rubyonrails.org/ticket/10382.

Rails 2.0 introduces a new (better) way to create your functional test cases. Instead of subclassing Test::Unit::TestCase directly, you can now subclass ActionController::TestCase, which will take care of the setting up the request/response environment for you. Here’s what a Rails 2.0 functional test might look like:

require File.dirname(__FILE__) + '/../test_helper'

class AuctionsControllerTest < ActionController::TestCase
  def test_should_require_login_for_create
    assert_login_required 'create'
  end
  # ...
end

Now, suppose you’re using the setup method to prepare a few things before each of your test methods run. You may remember that prior to this change, each functional test needed to define its own setup method in which it created a controller instance, and instances of TestRequest and TestResponse. Well, it still does. Only this setup method is moved to the new superclass, saving you the typing.

And therein lies the gotcha: if you go ahead and create your own setup method not realizing that you’ve inherited one, you’ll clobber the one defined in the superclass. So, when you’re defining your own setup method, remember to call the original one using super:

require File.dirname(__FILE__) + '/../test_helper'

class AuctionsControllerTest < ActionController::TestCase
  def setup
    super
    @current_user = login_as(:amy)
  end

  def test_should_require_login_for_create
    assert_login_required 'create'
  end
  # ...
end

Hopefully this will save you the 15 minutes I spent trying to figure out why this wasn’t working.

Seed Data in Rails

Friday, November 16

Someone asked me about this the other day, so I thought I’d write about it. In some of my applications, I need to “seed” the database with data. This might be a list of categories, sections, or other defaults.

There are a couple of ways you can do this. One way is to use migrations. You create records in your migration via ActiveRecord as you normally would, and when you run your migrations, the data is inserted. This works OK, except it obscures the location of the data. By the time you have a lot of migrations, you’re unlikely to remember that 003_create_categories.rb is also the place where you’re adding your default categories.

I like to think of migrations as being transient. As your schema grows and your project evolves, the chances of your migrations running perfectly from top to bottom diminish. When bootstrapping a database, it’s a much better idea to load the entire schema via db:schema:load than running through each transformation with migrations.

So, if we’re not using migrations for seed data, where do we keep it? I like to use YAML fixtures for this. You could use the test fixtures from test/fixtures, but this is an inappropriate location. If you were a new developer coming on to a project, why would you think to look in the test directory for seed data? Test fixtures are for your tests.

For seed data, I create a fixtures directory inside the existing db/ directory: db/fixtures. Then I use the following Rake task, called db:seed to load them:

namespace :db do
  desc "Load seed fixtures (from db/fixtures) into the current environment's database." 
  task :seed => :environment do
    require 'active_record/fixtures'
    Dir.glob(RAILS_ROOT + '/db/fixtures/*.yml').each do |file|
      Fixtures.create_fixtures('db/fixtures', File.basename(file, '.*'))
    end
  end
end

So, I might have something like db/fixtures/categories.yml. When I’m bootstrapping the project on a new machine (say, when deploying), I’d just do the following:

$ rake db:create:all
$ rake db:schema:load
$ rake db:seed

How are other folks out there dealing with seed data?

Edit in Textmate on Leopard

Thursday, November 15

After upgrading to Leopard, I noticed that the ‘Edit in TextMate’ command stopped working. This is a neat little hack that allows you to edit the contents of just about any Cocoa text control within TextMate. At first I was like, meh, no big deal. But after living without it for a few weeks, I realized just how useful it is.

A bit of googling revealed the cause: the rules governing Input Managers have changed in Leopard. Fortunately, TextMate creator Allan Odgaard has a detailed post on how to fix this.

http://blog.macromates.com/2007/inputmanagers-on-leopard

Related: SIMBL Hacks

Also affected by the change to Input Managers were some of my SIMBL hacks, specifically, the hack that I used to customize the default colours of Terminal.app: Mike Solomon’s TerminalColors. Being designed for Tiger’s Terminal.app, this wasn’t working for the new Leopard version.

Ciaran Walsh has re-hacked the hack, and I’m happy to report that I can finally read blue on a black background again. Thanks Ciaran!

http://ciaranwal.sh/2007/11/01/customising-colours-in-leopard-terminal

BTW, the new Terminal.app is completely awesome. Finally, a fast, tabbed terminal for OS X.

ActiveRecord, more quickly

Sunday, October 14

A while back I wrote about the ActiveRecord stub I use to easily hack around with AR. I’ve altered it to just create in-memory sqlite databases, and also to silence AR::Migration. Here’s the updated version:

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
ActiveRecord::Migration.verbose = false

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

Faster Fixtures in Rails 2.0

Monday, October 01

Rails 2.0 includes optimizations for the loading of fixtures in tests that offer a significant speedup. After updating one of my projects to the latest from svn, my tests are running up to 50% faster than before.

Units:
  • before: 17s
  • after: 12s
  • improvement: ~ 30%
Functionals:
  • before: 10s
  • after: 5s
  • improvement: ~ 50%

This particular project has a lot of tests, and to be honest, even 12 seconds is too slow for my tastes. But the improvement sure is welcome. In newer projects, I’m using UnitRecord to test without the database. Obviously, this is much faster. But for fixture-dependent projects, upgrading to Rails 2.0 will yield a significant improvement out of the box.

My .irbrc

Thursday, September 13

Pursuant to this ancient post regarding irb history and completion, here is my current .irbrc, now sporting a custom “Rails” section to make my life on the Rails console easier.

require 'irb/completion'
require 'irb/ext/save-history'

IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history" 
IRB.conf[:PROMPT_MODE]  = :SIMPLE

# Just for Rails...
if rails_env = ENV['RAILS_ENV']
  rails_root = File.basename(Dir.pwd)
  IRB.conf[:PROMPT] ||= {}
  IRB.conf[:PROMPT][:RAILS] = {
    :PROMPT_I => "#{rails_root}> ",
    :PROMPT_S => "#{rails_root}* ",
    :PROMPT_C => "#{rails_root}? ",
    :RETURN   => "=> %s\n" 
  }
  IRB.conf[:PROMPT_MODE] = :RAILS

  # Called after the irb session is initialized and Rails has
  # been loaded (props: Mike Clark).
  IRB.conf[:IRB_RC] = Proc.new do
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Base.instance_eval { alias :[] :find }
  end
end

I’m using the IRB.conf[:PROMPT] configuration option to create a custom Rails prompt that displays the current project name (which it assumes is the same as the PWD). Note that I’m also creating a convenience alias for AR::Base#find and enabling logging to STDOUT. Fun.

Impressed with Kijiji

Wednesday, September 12

I used Kijiji for the first time on the weekend, and I’m quite impressed. Kijiji is a free classified listing application created by the folks at eBay. It’s kind of like if craigslist and eBay had a baby.

My wife and I just bought a new house, and like most houses, it came with appliances. They weren’t the greatest, though, and we decided to replace them. The problem was, I didn’t know what to do with them. I don’t have a truck to take them to the dump, and I didn’t want to put them out on the end of the driveway and wait for them to be scavenged. The fridge I was able to give to a friend who needed it, but I didn’t know anyone who needed a stove. I didn’t want any money for it. I just wanted rid of it.

I thought of craigslist, but my local niagara.craigslist.com isn’t very active, and I doubted I’d get a reply in a timely fashion. My dad (of all people) suggested I try Kijiji. So, I posted it on Kijiji for and within an hour I had 10 replies. By the time I heard back from the first respondent, 3 more replies had come in. It was astonishingly fast for an area of this size. I really didn’t expect there to be so many users watching the listings.

The “winner” was from about 20 minutes away, and he came right over with his truck and his son, and loaded up the stove. We shook hands, he said thanks, and that was that.

Good things about Kijiji

One of my favorite things about Kijiji is how easy to use it is. You don’t even need to sign up to post a listing. Just your email will suffice, provided you confirm it. The listings themselves are sparse, consisting of just a title, a description, a price, and up to five image uploads. The images have a live preview and upload with a nice progress bar, so you can see what you’ve uploaded before you save your listing. If you enter your address with your listing, it will be automatically linked to a map, so people can tell whether or not you’re close by. (Unfortunately it’s a primitive non-Ajax mapquest map.)

The control panel (if you can call it that) is incredibly light. You can see your listings and choose to edit or delete them. You can also change your password. That’s it. When you’re done with your listing, you simply delete it.

People communicate with you by filling out the captcha-enabled contact form that’s on every listing. The email arrives with the sender’s name in the reply-to header, so you just reply to the email directly and call it a day. Nobody ever sees your email address unless you choose to reply.

They avoid listing spam by limiting the number of communities you can simultaneously post to, and by limiting the number of listings you can create in a single day. I found there was a lot less cruft in the listings than on craigslist. It was a lot more focused, and things are easy to find.

All in all, I found it a lot simpler and a lot more approachable than craigslist. Especially from a friendly-interface perspective. I could never get my parents to use craigslist, but they’ve already been using Kijiji for a few months and love it. They’ve sold a few antiques, a riding lawnmower, and have even made a few purchases.

Array#extract_options!

Tuesday, September 11

Rails (really, ActiveSupport) now sports a nifty new addition to Array to encapsulate the pattern of getting an options hash out of a variable number of arguments: extract_options!

If the last item in an array is a Hash, extract_options! removes and returns it. Otherwise, it just returns an empty Hash. This is primarily useful when you have a method that accepts a variable number of arguments (*args), the last of which is to be a hash of options. This is a common idiom in the Rails source code.

def options(*args)
  args.extract_options!
end

options # => {}
options(:name => 'foo', :format => 'text') # => { :name => 'foo', :format => 'text' }

This is cleaner than the way this is normally done:

def options(*args)
  options = args.last.is_a?(Hash) ? args.last : {}
end

You gotta love ActiveSupport.

ActiveRecord, quickly

Thursday, September 06

I often want to experiment with a database model using ActiveRecord, from outside the context of a Rails application. Ideally, I want to do this all in one file. Where I can see my migrations and my models at the same time and tweak as needed.

This is the stub I’ve been using lately for that task.

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(
  :adapter  => 'mysql',
  :database => 'example',
  :username => 'root',
  :host     => 'localhost'
)

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

I simply define all my migrations and each model class here, and then write a few tests to ensure things are working the way I expect. Changing the schema and adding/removing fields is a snap, and the database tables are dropped and recreated each time the file is run.

The tests are run selectively, and AR::Logger is set to STDOUT when $0 is irb because I often want to play with my creation interactively on the console. This helps me get a sense of how things work, and how my objects feel from an API perspective.

Here’s what the stub above looks like from an irb session:

$ irb
>> require 'example_models'
-- create_table("examples", {:force=>true})
   -> 0.1467s
-- initialize_schema_information()
   -> 0.0006s
-- columns("schema_info")
   -> 0.0027s
=> true

>> Example.create!
  Example Columns (0.064651)   SHOW FIELDS FROM examples
  SQL (0.000134)   BEGIN
  SQL (0.000316)   INSERT INTO examples (`updated_at`, `title`, `created_at`) VALUES('2007-09-06 11:14:05', NULL, '2007-09-06 11:14:05')
  SQL (0.000682)   COMMIT
=> #<Example id: 1, title: nil, created_at: "2007-09-06 11:14:05", updated_at: "2007-09-06 11:14:05">
>> 

I find this to be a really fast way to prototype ideas.

Links for Today: 2

Wednesday, September 05

Again with the links! Let’s see how long I keep this up…

Links for today: 1

Tuesday, September 04

And now for something new. Links for today. Whatever. It’s new to me.

  • Has finder: a new ActiveRecord extension that makes it easier than ever to create custom find and count queries. Looks better than both of scope-out and scoped-proxy.
  • Zooom: window stuff for OS X. Done right.
  • Webistrano: a web front-end for Capistrano.
  • Seesaw: high-availability Mongrel Packs.
  • Sphincter: ActiveRecord extension for full-text searching with Sphinx.

OM-Scribbish

Wednesday, August 08

I subscribe to Object Mentor’s blog, but I do all my reading via my newsreader and rarely visit the actual site. So, I was pleased when I went there today and noticed they were using a modified version of my Scribbish theme.

That theme really gets around!

Scribbish for Drupal

Tuesday, August 07

Alexandru Badiu wrote me on the weekend to say that he’s ported Scribbish to Drupal.

From the moment I saw your theme I fell in love with it. :) So I decided to port it to Drupal, my favourite blogging engine.

Visit the Drupal project page for more information:

http://drupal.org/project/scribbish

Beginning Rails: Published

Tuesday, July 24

I’m happy to announce the release of the book I’ve been working on with Cloves Carneiro Jr. and Hampton Catlin: “Beginning Rails: From Novice to Professional.” [cue: sound of champagne cork popping]

Beginning Rails is the practical starting point for anyone wanting to learn how to build dynamic web applications using the Rails framework for Ruby. You’ll learn how all of the components of Rails fit together and how you can leverage them to create sophisticated web applications with less code and more joy.

Pete wrote up a nice article on the Unspace blog, and Colin from CommunityLend made me blush a little with what he wrote. And leave it to Pete to stir up some debate about the trademark of the Rails logo and its resulting absence from the front cover of the book.

Strangely enough, I ordered a copy of the book from Amazon on Sunday and it was just delivered to my door. It’s funny because I haven’t even received my shipment from Apress. As soon as I noticed that Amazon had it in stock, I knew I’d get my grubby hands on a copy faster by ordering it from them. So, I paid $50 for my own book (including costly expedited shipping). Sigh. Hopefully I’ll get my shipment from Apress soon.

Capistrano multi-stage support

Monday, July 23

This is cool. The newest version of Capistrano now supports multiple stages. Like when you want to have different configuration for, say, production, testing, and staging. This is something I’ve been doing manually, which while it works, is not without its quirks.

It’s part of the capistrano extensions plugin (capistrano-ext), which is as easy as gem install capistrano-ext to obtain. Once installed, you simply require the multistage support in your deploy recipe file.

require 'capistrano/ext/multistage'

The best part is how stages are defined. All you have to do is place your stage-specific code in config/deploy/staging.rb and/or config/deploy/production.rb and it will get loaded automatically.

And if your stages aren’t named “production” and “staging,” you can define your own easily. You can then deploy to the staging server with cap staging deploy and to the production server with cap production deploy. And if you want the stating server to be the default, you can save yourself some typing by setting the default stage:

set :default_stage, 'staging'
require 'capistrano/ext/multistage'

For the full announcement, see Jamis Buck’s blog.