Skip to content

Commit

Permalink
Overrides become methods defined on each instance of the evaluator
Browse files Browse the repository at this point in the history
There were some big issues with trying to undefine specific methods on
the Evaluator. After investigating maybe inheriting from BasicObject (or
ActiveSupport::BasicObject since BasicObject is 1.9+), that turned out
to be too much of a pain because it undefines almost everything,
including class and a handful of other methods necessary for Evaluator
to work properly.

The second solution was to undefine all private methods. The problem is,
when other libraries defining methods (private or otherwise) on Object
are loaded *after* factory girl, those methods get added and Evaluator
sees those methods on Object. So, that solution didn't directly work either.

This commit removes undefining methods (the sole reason of which was to
capture with method_missing and process ourselves, returning the
override or cached value) and instead introduces a new concept -
iterating over each override and defining it as a method on the
evaluator INSTANCE. This means that overrides don't collide because
they're on the instance and we don't have to worry about undefining
methods so that method_missing kicks in. This is the most stable and
guaranteed way to get this to work because the overrides are applied to
each instance at runtime.

Closes #279, #285
  • Loading branch information
joshuaclayton committed Feb 3, 2012
1 parent 850116d commit f50550c
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 1 deletion.
10 changes: 9 additions & 1 deletion lib/factory_girl/evaluator.rb
Expand Up @@ -11,13 +11,21 @@ def self.attribute_list
end
end
end
undef_method(:id) if method_defined?(:id)

private_instance_methods.each do |method|
undef_method(method) unless method =~ /^__|initialize/
end

def initialize(build_strategy, overrides = {})
@build_strategy = build_strategy
@overrides = overrides
@cached_attributes = overrides

singleton = class << self; self end
@overrides.each do |name, value|
singleton.send :define_method, name, lambda { value }
end

@build_strategy.add_observer(CallbackRunner.new(self.class.callbacks, self))
end

Expand Down
47 changes: 47 additions & 0 deletions spec/acceptance/attribute_existing_on_object_spec.rb
Expand Up @@ -19,3 +19,50 @@
its(:link) { should == "http://example.com" }
its(:sleep) { should == -5 }
end

describe "assigning overrides that are also private methods on object" do
before do
define_model("Website", :format => :string, :y => :integer, :more_format => :string, :some_funky_method => :string)

Object.class_eval do
private
def some_funky_method(args)
end
end

FactoryGirl.define do
factory :website do
more_format { "format: #{format}" }
end
end
end

after do
Object.send(:undef_method, :some_funky_method)
end

subject { FactoryGirl.build(:website, :format => "Great", :y => 12345, :some_funky_method => "foobar!") }
its(:format) { should == "Great" }
its(:y) { should == 12345 }
its(:more_format) { should == "format: Great" }
its(:some_funky_method) { should == "foobar!" }
end

describe "accessing methods from the instance within a dynamic attribute that is also a private method on object" do
before do
define_model("Website", :more_format => :string) do
def format
"This is an awesome format"
end
end

FactoryGirl.define do
factory :website do
more_format { "format: #{format}" }
end
end
end

subject { FactoryGirl.build(:website) }
its(:more_format) { should == "format: This is an awesome format" }
end

0 comments on commit f50550c

Please sign in to comment.