1
0
Fork 0
mirror of https://github.com/thoughtbot/factory_bot.git synced 2022-11-09 11:43:51 -05:00

Evaluate sequences within the context of the Evaluator when possible

This fixes weird issues where methods invoked within sequences
(like `sprintf`) fail because these methods are being evaluated within
the context of the DefinitionProxy. With this change, invoking `#next`
on a sequence happens from the evaluator so if the scope is provided (it
usually will be), it'll run in the proper context. This means a couple
of oddities are fixed:

1. Developers can now refer to methods on the object instance, just like
in dynamic attributes:

    class User
      def company
        # company lookup
      end
    end

    FactoryGirl.define do
      factory :user do
        sequence(:job_title) {|n| "{title} #{n} at #{company.name}" }
      end
    end

2. Invoke methods typically available because the method is available on
Object (e.g. Kernel methods):

    FactoryGirl.define do
      factory :user do
        sequence(:last_4_ssn) {|n| sprintf '%04d', n }
      end
    end

[#466]
This commit is contained in:
Derek Prior and Josh Clayton 2013-01-11 12:05:17 -05:00 committed by Joshua Clayton
parent 2bccd270f8
commit bf29843d15
8 changed files with 98 additions and 7 deletions

View file

@ -1,5 +1,5 @@
PATH
remote: /Users/mjankowski/Development/OpenSource/factory_girl
remote: /Users/joshuaclayton/dev/gems/factory_girl
specs:
factory_girl (4.1.0)
activesupport (>= 3.0.0)

View file

@ -1,5 +1,5 @@
PATH
remote: /Users/mjankowski/Development/OpenSource/factory_girl
remote: /Users/joshuaclayton/dev/gems/factory_girl
specs:
factory_girl (4.1.0)
activesupport (>= 3.0.0)

View file

@ -1,5 +1,5 @@
PATH
remote: /Users/mjankowski/Development/OpenSource/factory_girl
remote: /Users/joshuaclayton/dev/gems/factory_girl
specs:
factory_girl (4.1.0)
activesupport (>= 3.0.0)

View file

@ -108,7 +108,7 @@ module FactoryGirl
# Except that no globally available sequence will be defined.
def sequence(name, *args, &block)
sequence = Sequence.new(name, *args, &block)
add_attribute(name) { sequence.next }
add_attribute(name) { increment_sequence(sequence) }
end
# Adds an attribute that builds an association. The associated instance will

View file

@ -42,10 +42,18 @@ module FactoryGirl
end
end
def respond_to_missing?(method_name, include_private = false)
@instance.respond_to?(method_name) || SyntaxRunner.new.respond_to?(method_name)
end
def __override_names__
@overrides.keys
end
def increment_sequence(sequence)
sequence.next(self)
end
def self.attribute_list
AttributeList.new.tap do |list|
attribute_lists.each do |attribute_list|

View file

@ -19,10 +19,16 @@ module FactoryGirl
end
end
def next
@proc ? @proc.call(@value.peek) : @value.peek
def next(scope = nil)
if @proc && scope
scope.instance_exec(value, &@proc)
elsif @proc
@proc.call(value)
else
value
end
ensure
@value.next
increment_value
end
def names
@ -31,6 +37,14 @@ module FactoryGirl
private
def value
@value.peek
end
def increment_value
@value.next
end
class EnumeratorAdapter
def initialize(value)
@value = value

View file

@ -0,0 +1,52 @@
require 'spec_helper'
describe 'sequences are evaluated in the correct context' do
before do
define_class("User") do
attr_accessor :id
def awesome
'aw yeah'
end
end
end
it 'builds a sequence calling sprintf correctly' do
FactoryGirl.define do
factory :sequence_with_sprintf, class: User do
sequence(:id) {|n| sprintf("foo%d", n) }
end
end
FactoryGirl.build(:sequence_with_sprintf).id.should == 'foo1'
end
it 'invokes the correct method on the instance' do
FactoryGirl.define do
factory :sequence_with_public_method, class: User do
sequence(:id) {|n| public_method(:awesome).call }
end
end
FactoryGirl.build(:sequence_with_public_method).id.should == 'aw yeah'
end
it 'invokes a method with no arguments on the instance' do
FactoryGirl.define do
factory :sequence_with_frozen, class: User do
sequence(:id) {|n| frozen? }
end
end
FactoryGirl.build(:sequence_with_frozen).id.should be_false
end
it 'allows direct reference of a method in a sequence' do
FactoryGirl.define do
factory :sequence_referencing_attribute_directly, class: User do
sequence(:id) {|n| "#{awesome}#{n}" }
end
end
FactoryGirl.build(:sequence_referencing_attribute_directly).id.should == 'aw yeah1'
end
end

View file

@ -75,4 +75,21 @@ describe FactoryGirl::Sequence do
expect { subject.next }.to raise_error(StopIteration)
end
end
describe "a custom sequence and scope" do
subject { FactoryGirl::Sequence.new(:name, 'A') {|n| "=#{n}#{foo}" } }
let(:scope) { stub('scope', foo: 'attribute') }
it 'increments within the correct scope' do
subject.next(scope).should == '=Aattribute'
end
describe 'when incrementing' do
before { subject.next(scope) }
it 'increments within the correct scope' do
subject.next(scope).should == '=Battribute'
end
end
end
end