Permalink
Browse files

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]
  • Loading branch information...
1 parent 2bccd27 commit bf29843d158ccad644a38dcf0473c901cedc2e08 Derek Prior and Josh Clayton committed with joshuaclayton Jan 11, 2013
@@ -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 @@ def method_missing(name, *args, &block)
# 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 @@ def method_missing(method_name, *args, &block)
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 @@ def initialize(name, *args, &proc)
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 @@ def names
private
+ def value
+ @value.peek
+ end
+
+ def increment_value
+ @value.next
+ end
+
class EnumeratorAdapter
def initialize(value)
@value = value
@@ -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 @@
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

0 comments on commit bf29843

Please sign in to comment.