When Rails Documentation Goes Awry, or Fixture Fun

I’ve been using Ruby on Rails for a little project that I’m working on while I seek employment. Considering that the last time that I actually played around with Rails was, oh, sometime back in 2005 when Rails was turning 1.0, I had a lot of cobwebs in my brain to dust off.

After getting up to speed with ActiveRecord migrations and some new model cardinality bits (specifically :has_many ... :through), I started writing some unit tests for my model objects. I remembered that Rails uses Fixtures for pre-populating the database with data for testing purposes, so I set about creating some. In the process, I discovered that a lot has changed in this area, especially being that one can now pretty much dispense with specifying ids for fixtured data and reference data by its fixture nickname. Not bad.

However, when it came time to use that data in the unit test code, I hit a few snags. The current documentation says that you can access your model’s fixture data as a ginormous hash.

Assume that you have a model class called Blob, defined thusly:

class Blob < ActiveRecord::Base
  attr_accessible :name, :description
end

And you have a fixture for the model in test/fixtures/blobs.yml:

foo:
  name: The Foo Object
  description: It's full of foo. And chunky bacon.

bar:
  name: Bar None
  description: Three lemmings run into a bar. Ouch. Ouch. Ouch.

According to the current documentation, you can access your data in your fixtures in the following ways:

  1. By using a hash stored in an instance variable named after the fixture, e.g. @blobs['foo']['name']
  2. By using an instance variable named after the record name, e.g. @foo.name
  3. By using a dynamically-created object, e.g. blobs(:foo).name

So, I decided to use the first way, assuming that it would work. However, anytime I tried to access the fixture data as @blobs, it threw an exception, as @blobs was nil. This was puzzling, as I thought I was doing everything right (although the docs mention that you have to use the fixture function to explicitly load the fixtures, and I wasn’t doing that initially, but changing my test code to include the call yielded the same results). The second way yielded a similar problem. The third method worked.

After asking a few times on #rubyonrails and hearing crickets, I decided to look at the code. After trawling around in the rails master branch, I discovered that at some point after the RDoc for the Fixture class had been written, non-instantiated fixtures were made the default. And, if you are using non-instantiated fixtures, you can only use method #3 to access the fixture’s data from within the test. Of course, the docs don’t mention that this default changed, nor does it mention that using instantiated fixtures incur a performance hit1 and thus have long since fallen out of favor2. But what really sucks is that this default changed in October 2005. The docs have been misleading for, oh, almost 3½ years.

Other observations:

  • All fixtures are loaded at test startup. You don’t need to specify fixtures :blobs in your test case.
  • Test methods now use the test method. Yeah, it’s syntactic sugar, but it’d be nice if the RDoc’s example kept up with the generated examples.

So, what do we need to do?

  1. I think updating the documentation to note the default behavior and move the non-instantiated behavior to be the primary means of accessing fixture data. Is there any reason to do this?
  2. Update the examples to show how to access the data using the default, non-instantiated fixture data.
  3. Note how to change the default behavior using self.use_instantiated_fixtures = true or self.use_instantiated_fixtures = :no_instances. Show how using each can enable alternative ways of accessing the fixtured data. Note any performance tradeoffs.

Here’s the Lighthouse issue for this. I’ll submit better documentation to go along with it. Consider this my broken window to fix3.

1 The docs do say “If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.” and then go on to mention that you can turn instantiated fixtures off, never mind that they already are off by default.

2 Some Googling revealed that many folks think Fixtures kind of suck, and say I should be mocking my model’s interaction with the database to speed up test performance. That’s not a bad idea, but it doesn’t mean you can just abandon Fixtures — and its documentation — altogether.

3 The quality of online Rails documentation varies from adequate to laughably bad and/or out of date. Seems like the best documentation is consigned to dead trees. If anything, the API docs should be correct if for no other reason that it is close to the code itself.

2009.01.30 · permalink