All the members of Los Lonely Geeks were hanging out in the Portland Campfire last night, contributing to the constant stream of YouTube videos, LOLCATS, LOST spoilers, and Ruby code. But in the middle of an intense academic colloquy regarding whether it is “I CAN HAS” or “I CAN HAZ,” I had a stroke of genius about something I’d been using for months: named_scope.

You’re probably familiar with Rails’ named_scope feature. It allows you create stackable “scopes” for your ActiveRecord queries (for example, User.active might find all those users whose account_expires date is after today or something). I’d always used it basically for that: scoping queries for finding records. But as I was creating a couple of new ones, I had a thought: I wonder if you can use them to create records with attributes, too.

Of course I had to find out.

Immediately, I opened script/console on an app and type a few things in:

>> Page.named_scope :awesome, {:conditions => {:title => "This is awesome."}}
=> (stuff)
>> Page.awesome.new
=> #<Page id: nil, title: "This is awesome.", created_at: nil, updated_at: nil>

Whoa. I always really like finding a new feature of something that I make a lot of use of. It’s like getting a whole new feature altogether.

So, of course, I shared this, which, of course, led to hacking. I tossed out the idea and Nicolas Sanguinetti (a.k.a., foca) tossed out a quick implementation within like 20 seconds (which now lives on as Kaleidoscope at his Github). We tinkered a bit after that, but in general the implementation didn’t change at all. When he put his implementation in Campfire, I’d just opened Textmate, so I jacked his implementation and within 30 minutes had built what I’m now calling foundry.

Foundry is another fixture replacement plugin (just like modelstubbing, fixturereplacement, factory_girl, machinist, and the trillions of others), but unlike most of these, it stays clear of ActiveRecord’s internals. It simply creates these scopes for you and lets you build objects off of them. For example, here’s a basic User factory:

model User do
  factory :valid_user, :name => "Jeremy", :login => "jeremy"
end

…which allows you to do this in a test:

User.valid_user.create

You can also provide blocks and some other crazy stuff, and you can check out the README for more information. But its implementation is powerful in its simplicity, and as evidence of that, you can do really flexible stuff like this:

require 'faker'

module Unique
  def unique(attr)
    if [:name, :first_name, :last_name].include?(attr)
      return Faker::Name.send(attr)
    elsif [:company_name, :company_bs, :company_catch_phrase].include?(attr)
      return Faker::Company.send(attr.to_s.gsub(/company_/, '')) # name is in user and company
    elsif [:email, :free_email, :user_name, :domain_name].include?(attr)
      return Faker::Internet.send(attr)
    else
      raise ArgumentError, "I'm not sure what random data you want by specifying #{attr}!"
    end
  end
end

module Foundry
  class Runner
    include Unique
  end
end

That will provide a unique method to your factories which you can use to generate names and such from Faker. Now, in your factories you can do:

# In your foundries.rb or whatever
model User do
  factory :unique do
   {:conditions => {:name => unique(:name), :login => unique(:user_name), :password => "1234"}}
  end
end

…and every time you generate a record, you’ll get unique data:

User.unique.create
# => #<User id: 12, name: "Wanda Sharp", login: "delia", :password => "1234", created_at: ..., updated_at: ...>
User.unique.create
# => #<User id: 13, name: "Alec Eichmann", login: "adam", :password => "1234", created_at: ..., updated_at: ...>
User.unique.create
# => #<User id: 14, name: "Derek Gleichner", login: "jared", :password => "1234", created_at: ..., updated_at: ...>

Awesome, eh? You can even chain these up just like you would normally do with named_scope's. So, you could have User.valid_user.with_role("admin").with_permissions(permissions) or something like that.

You can check out foundry at my Github, and if you dig it, follow it or fork it from its page.

3 Comments

  1. Jeremy Jeremy said on January 22nd, 2009

    Awesome, I love using named_scopes and this is a great way to provide clarity of intention in unit testing.

  2. Nick Kallen Nick Kallen said on January 24th, 2009

    Named Scope sucks.

  3. Ryan Ryan said on February 15th, 2009

    This is brilliant. I totally drank the cool aid on Thoughtbot’s Shoulda and Factory Girl, but I always wanted a way to use these kind of semantics when building test objects.

    Awesome.

Make your voice heard

Sorry, but comments are closed for this item.