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:
parent
2bccd270f8
commit
bf29843d15
8 changed files with 98 additions and 7 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
52
spec/acceptance/sequence_context_spec.rb
Normal file
52
spec/acceptance/sequence_context_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue