From fa36b1da4ab3ba9a0eb412d896b2e75d2b3ec68d Mon Sep 17 00:00:00 2001 From: Joe Ferris Date: Sun, 1 Jun 2008 10:46:50 -0700 Subject: [PATCH] Added support for sequences --- README | 17 +++++++++++++ lib/factory_girl.rb | 1 + lib/factory_girl/factory.rb | 38 ++++++++++++++++++++++++++++- lib/factory_girl/sequence.rb | 17 +++++++++++++ test/factory_test.rb | 47 ++++++++++++++++++++++++++++++++++++ test/integration_test.rb | 34 +++++++++++++++++++++++++- test/sequence_test.rb | 29 ++++++++++++++++++++++ 7 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 lib/factory_girl/sequence.rb create mode 100644 test/sequence_test.rb diff --git a/README b/README index 45e5b44..aefd191 100644 --- a/README +++ b/README @@ -71,6 +71,23 @@ When using the association method, the same build strategy (build, create, or at post.new_record? # => true post.author.new_record # => true +== Sequences + +Unique values in a specific format (for example, e-mail addresses) can be +generated using sequences. Sequences are defined by calling Factory.sequence, +and values in a sequence are generated by calling Factory.next: + + # Defines a new sequence + Factory.sequence :email do |n| + "person#{n}@example.com" + end + + Factory.next :email + # => "person1@example.com" + + Factory.next :email + # => "person2@example.com" + == Using factories # Build and save a User instance diff --git a/lib/factory_girl.rb b/lib/factory_girl.rb index 0fe24fb..f32d0e9 100644 --- a/lib/factory_girl.rb +++ b/lib/factory_girl.rb @@ -1,6 +1,7 @@ require 'activesupport' require 'factory_girl/factory' require 'factory_girl/attribute_proxy' +require 'factory_girl/sequence' # Shortcut for Factory.create. # diff --git a/lib/factory_girl/factory.rb b/lib/factory_girl/factory.rb index 0ec799f..f42f344 100644 --- a/lib/factory_girl/factory.rb +++ b/lib/factory_girl/factory.rb @@ -1,7 +1,8 @@ class Factory - cattr_accessor :factories #:nodoc: + cattr_accessor :factories, :sequences #:nodoc: self.factories = {} + self.sequences = {} attr_reader :name @@ -24,6 +25,41 @@ class Factory self.factories[name] = instance end + # Defines a new sequence that can be used to generate unique values in a specific format. + # + # Arguments: + # name: (Symbol) + # A unique name for this sequence. This name will be referenced when + # calling next to generate new values from this sequence. + # block: (Proc) + # The code to generate each value in the sequence. This block will be + # called with a unique number each time a value in the sequence is to be + # generated. The block should return the generated value for the + # sequence. + # + # Example: + # + # Factory.sequence(:email) {|n| "somebody_#{n}@example.com" } + def self.sequence (name, &block) + self.sequences[name] = Sequence.new(&block) + end + + # Generates and returns the next value in a sequence. + # + # Arguments: + # name: (Symbol) + # The name of the sequence that a value should be generated for. + # + # Returns: + # The next value in the sequence. (Object) + def self.next (sequence) + unless self.sequences.key?(sequence) + raise "No such sequence: #{sequence}" + end + + self.sequences[sequence].next + end + def build_class #:nodoc: @build_class ||= @options[:class] || name.to_s.classify.constantize end diff --git a/lib/factory_girl/sequence.rb b/lib/factory_girl/sequence.rb new file mode 100644 index 0000000..05ba6a3 --- /dev/null +++ b/lib/factory_girl/sequence.rb @@ -0,0 +1,17 @@ +class Factory + + class Sequence + + def initialize (&proc) + @proc = proc + @value = 0 + end + + def next + @value += 1 + @proc.call(@value) + end + + end + +end diff --git a/test/factory_test.rb b/test/factory_test.rb index 943abaa..9253a2d 100644 --- a/test/factory_test.rb +++ b/test/factory_test.rb @@ -52,6 +52,28 @@ class FactoryTest < Test::Unit::TestCase end + context "defining a sequence" do + + setup do + @sequence = mock('sequence') + @name = :count + Factory::Sequence.stubs(:new).returns(@sequence) + end + + should "create a new sequence" do + Factory::Sequence.expects(:new).with().returns(@sequence) + Factory.sequence(@name) + end + + should "use the supplied block as the sequence generator" do + Factory::Sequence.stubs(:new).yields(1) + yielded = false + Factory.sequence(@name) {|n| yielded = true } + assert yielded + end + + end + context "a factory" do setup do @@ -283,4 +305,29 @@ class FactoryTest < Test::Unit::TestCase end + context "after defining a sequence" do + + setup do + @sequence = mock('sequence') + @name = :test + @value = '1 2 5' + + @sequence. stubs(:next).returns(@value) + Factory::Sequence.stubs(:new). returns(@sequence) + + Factory.sequence(@name) {} + end + + should "call next on the sequence when sent next" do + @sequence.expects(:next) + + Factory.next(@name) + end + + should "return the value from the sequence" do + assert_equal @value, Factory.next(@name) + end + + end + end diff --git a/test/integration_test.rb b/test/integration_test.rb index 86684ad..e211fb9 100644 --- a/test/integration_test.rb +++ b/test/integration_test.rb @@ -19,7 +19,11 @@ class IntegrationTest < Test::Unit::TestCase f.first_name 'Ben' f.last_name 'Stein' f.admin true - f.email {|a| "#{a.first_name}.#{a.last_name}@example.com".downcase } + f.email { Factory.next(:email) } + end + + Factory.sequence :email do |n| + "somebody#{n}@example.com" end end @@ -108,4 +112,32 @@ class IntegrationTest < Test::Unit::TestCase end + context "an attribute generated by a sequence" do + + setup do + @email = Factory.attributes_for(:admin)[:email] + end + + should "match the correct format" do + assert_match /^somebody\d+@example\.com$/, @email + end + + context "after the attribute has already been generated once" do + + setup do + @another_email = Factory.attributes_for(:admin)[:email] + end + + should "match the correct format" do + assert_match /^somebody\d+@example\.com$/, @email + end + + should "not be the same as the first generated value" do + assert_not_equal @email, @another_email + end + + end + + end + end diff --git a/test/sequence_test.rb b/test/sequence_test.rb new file mode 100644 index 0000000..05aa42d --- /dev/null +++ b/test/sequence_test.rb @@ -0,0 +1,29 @@ +require(File.join(File.dirname(__FILE__), 'test_helper')) + +class SequenceTest < Test::Unit::TestCase + + context "a sequence" do + + setup do + @sequence = Factory::Sequence.new {|n| "=#{n}" } + end + + should "start with a value of 1" do + assert_equal "=1", @sequence.next + end + + context "after being called" do + + setup do + @sequence.next + end + + should "use the next value" do + assert_equal "=2", @sequence.next + end + + end + + end + +end