Unique Marketing, Guaranteed Results.

Smarter Sequencing in Factory Girl

May 29th, 2009 by Brett Rasmussen

Hal Shearer and I monkey-patched Factory Girl’s sequencing capabilities to allow for pre-defined enumerations to loop through, instead of just infinitely incrementing numbers.

So instead of doing this:

  Factory.sequence :email do |n|
    "person#{n}@example.com"
  end

you could do something like this:

  Factory.sequence(:email, ['angela', 'brett', 'alec']) do |name|
    "#{name}@example.com"
  end

It will start over at the beginning when it’s gone through all of them:

>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"

You can also hand it a range (the internal implementation on this is none too efficient, so don’t give it billions at a time):

Factory.sequence(:email, 50..60) do |n|
  "user_#{n}@example.com"
end

>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"

The infinitely incrementing counter is still available if you want it:

Factory.sequence(:email, %w[angela brett alec]) do |name,i|
  "#{name}_#{i}@example.com"
end

>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"
>> Factory.next :email
=> "[email protected]"

This sort of thing is useful when you want two different factories to use the same sequence and have some overlap between the two groups. For example, we need a bunch of email addresses to test on, many of which share the same domain:

Factory.sequence(:name, %w[angela brett alec hal debbie tracey jared]) do |name,i|
  "#{name}_#{i}"
end

Factory.sequence(:domain, %w[something.com example.com mydomain.com]) do |domain|
  domain
end

Factory.define(:email_address) do |f|
  f.address { "#{Factory.next(:name)}@#{Factory.next(:domain)}" }
end

>> 20.times { ea = Factory.build :email_address; puts ea.address }
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

For our last trick, the reset method returns both the looping index and the infinite counter back to zero:

>> Factory.reset :name
>> Factory.next :name
=> "angela_0"

Here’s the code to make it happen:

class Factory
  def self.sequence(sequence_name, enum = nil, &blk)
    @@sequences ||= {}

    enum = enum.to_a

    @@sequences[sequence_name] = {
      :enum => enum,
      :index => 0,
      :infinite_counter => 0,
      :template  => blk
    }
  end

  def self.next(sequence_name)
    seq = @@sequences[sequence_name]

    retval = case seq[:template].arity
      when 1
        seq[:template].call(seq[:enum][seq[:index]])
      when 2
        seq[:template].call(seq[:enum][seq[:index]], seq[:infinite_counter])
    end

    seq[:index] = (seq[:index]+1 == seq[:enum].size) ? 0 : seq[:index]+1
    seq[:infinite_counter] += 1
    @@sequences[sequence_name] = seq
    retval
  end

  def self.reset(sequence_name)
    @@sequences[sequence_name][:index] = 0
    @@sequences[sequence_name][:infinite_counter] = 0
  end
end

Just put that into some file–perhaps in your rails lib directory–and make sure that file gets required–probably in your rails config/environment.rb. When doing it by hand like this, you’ll want to make sure your library file is loaded after the factory_girl gem is loaded, or you’ll get weirdness like methods you’ve overridden acting in non-overridden ways and the like; config.after_initialize in your environment.rb’s Rails::Initializer block is your friend.

You can also now use the gem BrettRasmussen-factory_girl from gems.github.com. I mean to submit it as a patch back to the original factory_girl, which I’m sure I’ll have time to do Any Day Now.

Filed under: Programming,Tutorials — Tags: , — Brett Rasmussen @ 12:56 pm on May 29, 2009

2 Comments

  1. Thanks for implementing a Genius idea and blogging about it! You guys rock!!! Go PMA!!

    Comment by Jared Dobson — May 29, 2009 @ 2:06 pm

  2. […] Alphabet sequences with factorygirl Posted on September 3, 2010 I was trying to use this in a factorygirl class to create test data with names like Product A, Product B etc. This isn't very sophisticated. If you want some smarter dummy data generation with factorygirl, check out this post at the PMA media group. […]

    Pingback by Alphabet sequences with factorygirl « SQUARISM — September 3, 2010 @ 11:37 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

Copyright © 2005-2016 PMA Media Group. All Rights Reserved &nbsp