Skip to content

Commit

Permalink
Move class creation/handling to an anonymous evaluator
Browse files Browse the repository at this point in the history
This allows for Attribute#to_proc to not require a proxy to be passed to
return a Proc. It also allows for removal of Attribute#add_to
  • Loading branch information
joshuaclayton committed Dec 1, 2011
1 parent b339c8f commit 3282eea
Show file tree
Hide file tree
Showing 19 changed files with 165 additions and 91 deletions.
1 change: 1 addition & 0 deletions lib/factory_girl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'factory_girl/registry'
require 'factory_girl/null_factory'
require 'factory_girl/factory'
require 'factory_girl/anonymous_evaluator'
require 'factory_girl/attribute'
require 'factory_girl/callback'
require 'factory_girl/declaration_list'
Expand Down
34 changes: 34 additions & 0 deletions lib/factory_girl/anonymous_evaluator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module FactoryGirl
class AnonymousEvaluator
attr_reader :attributes

def initialize
@attributes = []
end

def set(attribute, value)
define_attribute(attribute, value)
@attributes << attribute
end

def set_ignored(attribute, value)
define_attribute(attribute, value)
end

def evaluator
@evaluator ||= Class.new do
def initialize
@cached_attributes = {}
end
end
end

private

def define_attribute(attribute, value)
evaluator.send(:define_method, attribute) {
@cached_attributes[attribute] ||= instance_exec(&value)
}
end
end
end
10 changes: 1 addition & 9 deletions lib/factory_girl/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,7 @@ def initialize(name, ignored)
ensure_non_attribute_writer!
end

def add_to(proxy)
if @ignored
proxy.set_ignored(self, to_proc(proxy))
else
proxy.set(self, to_proc(proxy))
end
end

def to_proc(proxy)
def to_proc
lambda { }
end

Expand Down
4 changes: 2 additions & 2 deletions lib/factory_girl/attribute/association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ def initialize(name, factory, overrides)
@overrides = overrides
end

def to_proc(proxy)
def to_proc
factory = @factory
overrides = @overrides
lambda { proxy.association(factory, overrides) }
lambda { association(factory, overrides) }
end

def association?
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/attribute/dynamic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def initialize(name, ignored, block)
@block = block
end

def to_proc(proxy)
def to_proc
block = @block

lambda {
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/attribute/sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def initialize(name, sequence, ignored)
@sequence = sequence
end

def to_proc(proxy)
def to_proc
sequence = @sequence
lambda { FactoryGirl.generate(sequence) }
end
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/attribute/static.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def initialize(name, value, ignored)
@value = value
end

def to_proc(proxy)
def to_proc
value = @value
lambda { value }
end
Expand Down
12 changes: 10 additions & 2 deletions lib/factory_girl/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,19 @@ def handle_attribute_with_overrides(attribute)
end

def add_static_attribute(attr, val, ignored = false)
Attribute::Static.new(attr, val, ignored).add_to(proxy)
set_attribute_on_proxy(Attribute::Static.new(attr, val, ignored))
end

def handle_attribute_without_overrides(attribute)
attribute.add_to(proxy)
set_attribute_on_proxy(attribute)
end

def set_attribute_on_proxy(attribute)
if attribute.ignored
proxy.set_ignored(attribute)
else
proxy.set(attribute)
end
end

def proxy
Expand Down
58 changes: 24 additions & 34 deletions lib/factory_girl/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ module FactoryGirl
class Proxy #:nodoc:
def initialize(klass, callbacks = [])
@callbacks = process_callbacks(callbacks)
@proxy = ObjectWrapper.new(klass)
@proxy = ObjectWrapper.new(klass, self)
end

delegate :get, :set, :set_ignored, :anonymous_instance, :to => :@proxy

def run_callbacks(name)
if @callbacks[name]
@callbacks[name].each do |callback|
callback.run(result_instance, anonymous_instance)
callback.run(result_instance, @proxy.anonymous_instance)
end
end
end

def set_ignored(attribute)
@proxy.set_ignored(attribute.name, attribute.to_proc)
end

def set(attribute)
@proxy.set(attribute.name, attribute.to_proc)
end

# Generates an association using the current build strategy.
#
# Arguments:
Expand Down Expand Up @@ -85,14 +91,21 @@ def result_hash
end

class ObjectWrapper
def initialize(klass)
def initialize(klass, proxy)
@klass = klass
@attributes = []
@proxy = proxy
@assigned_attributes = []

@evaluator = AnonymousEvaluator.new
@evaluator.evaluator.send(:define_method, :association) { |*args|
proxy.association(*args)
}
end

delegate :set, :set_ignored, :attributes, :to => :@evaluator

def to_hash
@attributes.inject({}) do |result, attribute|
attributes.inject({}) do |result, attribute|
result[attribute] = get(attribute)
result
end
Expand All @@ -104,44 +117,21 @@ def object
@object
end

def set(attribute, value)
define_attribute(attribute, value)
@attributes << attribute.name
end

def set_ignored(attribute, value)
define_attribute(attribute, value)
end

def get(attribute)
anonymous_instance.send(attribute)
end

def anonymous_instance
@anonymous_instance ||= anonymous_class.new
@anonymous_instance ||= @evaluator.evaluator.new
end

private

def define_attribute(attribute, value)
anonymous_class.send(:define_method, attribute.name) {
@cached_attributes[attribute.name] ||= instance_exec(&value)
}
end

def assign_object_attributes
(@attributes - @assigned_attributes).each do |attribute|
(attributes - @assigned_attributes).each do |attribute|
@assigned_attributes << attribute
@object.send("#{attribute}=", get(attribute))
end
end

def anonymous_class
@anonymous_class ||= Class.new do
def initialize
@cached_attributes = {}
end
end
def get(attribute)
anonymous_instance.send(attribute)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/factory_girl/proxy/attributes_for.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module FactoryGirl
class Proxy #:nodoc:
class AttributesFor < Proxy #:nodoc:
def set(attribute, value)
def set(attribute)
return if attribute.is_a? Attribute::Association
super
end
Expand Down
65 changes: 65 additions & 0 deletions spec/factory_girl/anonymous_evaluator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require "spec_helper"

describe FactoryGirl::AnonymousEvaluator do
its(:attributes) { should == [] }
end

describe FactoryGirl::AnonymousEvaluator, "#set" do
let(:attribute) { :one }
let(:value) { lambda { @result ||= 0; @result += 1 } }

def set_attribute
subject.set(attribute, value)
end

it "adds the method to the evaluator" do
set_attribute
subject.evaluator.new.one.should == 1
end

it "tracks the attribute" do
set_attribute
subject.attributes.should == [attribute]
end

it "caches the result" do
set_attribute
subject.evaluator.new.tap do |obj|
obj.one.should == 1
obj.one.should == 1
end
end

it "evaluates the block in the context of the evaluator" do
set_attribute
subject.set(:two, lambda { one + 1 })
subject.evaluator.new.two.should == 2
end
end

describe FactoryGirl::AnonymousEvaluator, "#set_ignored" do
let(:attribute) { :one }
let(:value) { lambda { @result ||= 0; @result += 1 } }

def set_attribute
subject.set_ignored(attribute, value)
end

it "adds the method to the evaluator" do
set_attribute
subject.evaluator.new.one.should == 1
end

it "does not track the attribute" do
set_attribute
subject.attributes.should == []
end

it "caches the result" do
set_attribute
subject.evaluator.new.tap do |obj|
obj.one.should == 1
obj.one.should == 1
end
end
end
9 changes: 5 additions & 4 deletions spec/factory_girl/attribute/association_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
let(:association) { stub("association") }
let(:proxy) { stub("proxy", :association => association) }

subject { FactoryGirl::Attribute::Association.new(name, factory, overrides) }
subject { FactoryGirl::Attribute::Association.new(name, factory, overrides) }
before { subject.stubs(:association => association) }

it { should be_association }
its(:name) { should == name }

it "builds the association when calling the proc" do
subject.to_proc(proxy).call.should == association
subject.to_proc.call.should == association
end

it "builds the association when calling the proc" do
subject.to_proc(proxy).call
proxy.should have_received(:association).with(factory, overrides)
subject.to_proc.call
subject.should have_received(:association).with(factory, overrides)
end
end

Expand Down
17 changes: 8 additions & 9 deletions spec/factory_girl/attribute/dynamic_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

describe FactoryGirl::Attribute::Dynamic do
let(:name) { :first_name }
let(:proxy) { stub("proxy", :set => nil) }
let(:block) { lambda { } }

subject { FactoryGirl::Attribute::Dynamic.new(name, false, block) }
Expand All @@ -13,36 +12,36 @@
let(:block) { lambda { "value" } }

it "returns the value when executing the proc" do
subject.to_proc(proxy).call.should == "value"
subject.to_proc.call.should == "value"
end
end

context "with a block returning its block-level variable" do
let(:block) { lambda {|thing| thing } }

it "returns self when executing the proc" do
subject.to_proc(proxy).call.should == subject
subject.to_proc.call.should == subject
end
end

context "with a block referencing an attribute on the proxy" do
let(:block) { lambda { attribute_defined_on_proxy } }
context "with a block referencing an attribute on the attribute" do
let(:block) { lambda { attribute_defined_on_attribute } }
let(:result) { "other attribute value" }

before do
subject.stubs(:attribute_defined_on_proxy => result)
subject.stubs(:attribute_defined_on_attribute => result)
end

it "evaluates the attribute from the proxy" do
subject.to_proc(proxy).call.should == result
it "evaluates the attribute from the attribute" do
subject.to_proc.call.should == result
end
end

context "with a block returning a sequence" do
let(:block) { lambda { Factory.sequence(:email) } }

it "raises a sequence abuse error" do
expect { subject.to_proc(proxy).call }.to raise_error(FactoryGirl::SequenceAbuseError)
expect { subject.to_proc.call }.to raise_error(FactoryGirl::SequenceAbuseError)
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions spec/factory_girl/attribute/sequence_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
let(:sequence_name) { :name }
let(:name) { :first_name }
let(:sequence) { FactoryGirl::Sequence.new(sequence_name, 5) { |n| "Name #{n}" } }
let(:proxy) { stub("proxy") }

subject { FactoryGirl::Attribute::Sequence.new(name, sequence_name, false) }
before { FactoryGirl.register_sequence(sequence) }

its(:name) { should == name }

it "assigns the next value in the sequence" do
subject.to_proc(proxy).call.should == "Name 5"
subject.to_proc.call.should == "Name 5"
end
end
Loading

0 comments on commit 3282eea

Please sign in to comment.