What's New in Edge Rails: Fixtures Just Got a Whole Lot Easier

Posted by ryan
at 8:32 AM on Friday, October 26, 2007



There are a plethora of fixture plugins that attempt to make your test fixtures less of a pain in the arse to work with. And most do succeed in one manner or another.

Well one of the nicest fixture plugins, called Rathole, has just been sucked down into rails – and it’s a doozy. Let’s summarize so you can decide whether to dive in or not.

Let’s use the following model for this example:

1
2
class Company < ActiveRecord::Base; has_many :employees; end
class Employee < ActiveRecord::Base; belongs_to :company end

Just a simple one-to-many association between a company and its employees, easy enough. But when we get into setting up our fixtures there’s a lot of non-essential stuff and easy to mess up ids we have to manually specify. And all this just gets exponentially hairier as the data-set increases.

companies.yml
1
2
3
4
5
yfactorial:
  id: 1
  name: yFactorial, LLC
  created_at: <%= Time.now %>
  updated_at: <%= Time.now %>
employees.yml
1
2
3
4
5
6
ryan:
  id: 1
  name: Ryan Daigle
  company_id: 1
  created_at: <%= Time.now %>
  updated_at: <%= Time.now %>

With Rathole’s features you no longer need to manually reconcile the cross referenced ids and to set the auto-magic date column values. Watch carefully what your fixtures can be when they grow up:

companies.yml
1
2
yfactorial:
  name: yFactorial, LLC
employees.yml
1
2
3
ryan:
  name: Ryan Daigle
  company: yfactorial
Take special note of the following changes between the two fixture-sets:
  • You no longer have to specify the id for each record. That’s automatically done for you as the hash of the name of the record (so the integer hash of "yfactorial" becomes that record’s id)
  • No need to specify the auto-magic date field values anymore. All the created_xx and updated_xx fields will be automatically populated with Time.now
  • When referencing other record ids (as in employees.company_id), you can just put the fixture name and it will resolve that column to be the pk value of the referenced record. This is what allowed me to change the company_id: 1 line in employees.yml to be company: yfactorial.

Note: Since the primary keys of each record are calculated (as a hash of the fixture name), the fixture doesn’t need to be loaded before knowing what it’s pk value will be. This resolves a lot of the recursive dependency and ordering issues you might’ve otherwise encountered with an approach such as this.

But there’s more… Foreign key references get especially twisted when you have join tables that govern your has_and_belongs_to_many associations. So assume a company has_and_belongs_to_many industry_associations:

1
2
3
4
5
6
7
8
class Company < ActiveRecord::Base
  has_and_belongs_to_many :industry_associations,
                          :join_table => 'company_industry_associations'
end
class IndustryAssociation < ActiveRecord::Base
  has_and_belongs_to_many :companies,
                          :join_table => 'company_industry_associations'
end

Instead of having to write our own company_industry_associations.yml fixture file, we can just reference the collection of associations a company belongs to inline in companies.yml:

companies.yml
1
2
3
yfactorial:
  name: yFactorial, LLC
  industry_associations: ruby, webservices

Assuming there’s an industry_associations.yml fixture file that specifies records named ruby and webservices, the habtm join table will be automatically populated for you.

This is a really big win for you fixture users (of which I am one). Perhaps those that have turned their nose up at fixtures will take a second look? Really great functionality – thanks, John, for the contribution!

Take a look at the bottom of the plugin’s README file to see how to setup default values and how to fetch the pks of other fixtures

tags: ruby, rubyonrails

Comments

Leave a response

  1. Luke FranclOctober 26, 2007 @ 11:46 AM

    Awesome! Fixtures are annoying to get setup but I think they’re useful for a lot of testing scenarios. This makes them a lot less annoying.

  2. Andrew VitOctober 27, 2007 @ 05:19 AM

    I’m not a fixtures fan, I thought fixtures were passé, but this looks really nice…

    One of my concerns with fixtures has been speed, and I’ve been tending to use mocha and my custom Factory module to instantiate or create models, avoiding the database where it isn’t necessary. Still, I do use the database to test some associations and custom finders so a good number of my tests do rely on fixtures.

    I’m curious to know how the automatic foreign key syntax affects the overall speed: does it incur another query? Does that matter, overall?

    Another thing I dislike about fixtures is that the actual relevant data being tested isn’t clearly visible in the test, or even all in one place for that matter. Creating specific scenarios like: posts(:active_and_belonging_to_inactive_category) etc. gets unwieldy and hard to comprehend when reading the test. For this reason I tend to prefer creating the models within the test method itself instead of using fixtures.

    http://code.google.com/p/fixture-scenarios/ Something like this looks like it would be helpful for these testing concerns; I should look into it…

    I still consider myself a TDD rookie, and I’d love to know more about best practices for testing in standard Rails TestUnit because I find the whole experience with fixtures really clumsy.

    I wonder how many Rails TDDers find the need to extend the built-in testing toolkit with other tools to make it usable? Or have all the big boys & girls just gone to RSpec?

  3. John BarnetteOctober 27, 2007 @ 05:45 PM

    Ryan, thanks for the writeup! One quick note: Although Rathole’s functionality has been rolled into Rails proper for the 2.0 release, I’ll be maintaining and improving the plugin for 1.2.x.

    Andrew, the automatic ID/FK support doesn’t pay any penalty as far as extra queries go. The only thing in Rathole that’s even remotely magical is that we can figure out any fixture’s ID (in any table) knowing only its label:

    foo:
      # id: 876516207 (the ID for any fixture named "foo" will always be this number)

    This is accomplished by simply hashing the label:

    "foo".hash.abs => 876516207

    Once ID’s are consistent, everything else follows.

    () ...assuming the same platform, version of Ruby, etc.

  4. John BarnetteOctober 27, 2007 @ 05:47 PM

    Hmf. There were several asterisks in there: I wasn’t expecting styling. :)

  5. James HillOctober 28, 2007 @ 03:07 PM

    Oh so happy about this, it’ll make fixtures so much more usable.

    brilliant!

  6. Tiago MacedoOctober 31, 2007 @ 10:28 AM

    I’m using this on edge but it’s a shame that polymorphic relations aren’t supported.

  7. David LowenfelsNovember 15, 2007 @ 08:06 PM

    I just created a patch for polymorphic associations…

    http://blog.internautdesign.com/2007/11/16/polymorphic-relations-patch-for-rathole-plugin

  8. SoleoneDecember 08, 2007 @ 08:35 AM

    Very nice! This and the Sexy Migration feature of Rails 2.0 really are nice simplifications. And I really like the term Foxy Fixtures, should be the official name for it :)