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
|
PATH
|
||||||
remote: /Users/mjankowski/Development/OpenSource/factory_girl
|
remote: /Users/joshuaclayton/dev/gems/factory_girl
|
||||||
specs:
|
specs:
|
||||||
factory_girl (4.1.0)
|
factory_girl (4.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
PATH
|
PATH
|
||||||
remote: /Users/mjankowski/Development/OpenSource/factory_girl
|
remote: /Users/joshuaclayton/dev/gems/factory_girl
|
||||||
specs:
|
specs:
|
||||||
factory_girl (4.1.0)
|
factory_girl (4.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
PATH
|
PATH
|
||||||
remote: /Users/mjankowski/Development/OpenSource/factory_girl
|
remote: /Users/joshuaclayton/dev/gems/factory_girl
|
||||||
specs:
|
specs:
|
||||||
factory_girl (4.1.0)
|
factory_girl (4.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
|
|
@ -108,7 +108,7 @@ module FactoryGirl
|
||||||
# Except that no globally available sequence will be defined.
|
# Except that no globally available sequence will be defined.
|
||||||
def sequence(name, *args, &block)
|
def sequence(name, *args, &block)
|
||||||
sequence = Sequence.new(name, *args, &block)
|
sequence = Sequence.new(name, *args, &block)
|
||||||
add_attribute(name) { sequence.next }
|
add_attribute(name) { increment_sequence(sequence) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds an attribute that builds an association. The associated instance will
|
# Adds an attribute that builds an association. The associated instance will
|
||||||
|
|
|
@ -42,10 +42,18 @@ module FactoryGirl
|
||||||
end
|
end
|
||||||
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__
|
def __override_names__
|
||||||
@overrides.keys
|
@overrides.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def increment_sequence(sequence)
|
||||||
|
sequence.next(self)
|
||||||
|
end
|
||||||
|
|
||||||
def self.attribute_list
|
def self.attribute_list
|
||||||
AttributeList.new.tap do |list|
|
AttributeList.new.tap do |list|
|
||||||
attribute_lists.each do |attribute_list|
|
attribute_lists.each do |attribute_list|
|
||||||
|
|
|
@ -19,10 +19,16 @@ module FactoryGirl
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def next
|
def next(scope = nil)
|
||||||
@proc ? @proc.call(@value.peek) : @value.peek
|
if @proc && scope
|
||||||
|
scope.instance_exec(value, &@proc)
|
||||||
|
elsif @proc
|
||||||
|
@proc.call(value)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
ensure
|
ensure
|
||||||
@value.next
|
increment_value
|
||||||
end
|
end
|
||||||
|
|
||||||
def names
|
def names
|
||||||
|
@ -31,6 +37,14 @@ module FactoryGirl
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def value
|
||||||
|
@value.peek
|
||||||
|
end
|
||||||
|
|
||||||
|
def increment_value
|
||||||
|
@value.next
|
||||||
|
end
|
||||||
|
|
||||||
class EnumeratorAdapter
|
class EnumeratorAdapter
|
||||||
def initialize(value)
|
def initialize(value)
|
||||||
@value = 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)
|
expect { subject.next }.to raise_error(StopIteration)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue