Permalink
Browse files

Overrides become methods defined on each instance of the evaluator

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 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. Although not nececessarily as
performant, 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 (again)
  • Loading branch information...
1 parent 5f95b96 commit d4045fe6bc4fee330924580d53486e5aa676f9ce @joshuaclayton joshuaclayton committed Feb 2, 2012
Showing with 24 additions and 14 deletions.
  1. +5 −6 lib/factory_girl/evaluator.rb
  2. +19 −8 spec/acceptance/attribute_existing_on_object_spec.rb
@@ -12,17 +12,16 @@ def self.attribute_list
end
end
- private_instance_methods.each do |method|
- undef_method(method) unless method =~ /^__|initialize/
- end
-
- undef_method(:id) if method_defined?(:id)
-
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
@@ -2,7 +2,13 @@
describe "declaring attributes on a Factory that are private methods on Object" do
before do
- define_model("Website", :system => :boolean, :link => :string, :sleep => :integer, :format => :string, :y => :integer, :more_format => :string)
+ define_model("Website", :system => :boolean, :link => :string, :sleep => :integer, :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
@@ -14,12 +20,17 @@
end
end
- subject { FactoryGirl.build(:website, :sleep => -5, :format => "Great", :y => 12345) }
+ after do
+ Object.send(:undef_method, :some_funky_method)
+ end
+
+ subject { FactoryGirl.build(:website, :sleep => -5, :format => "Great", :y => 12345, :some_funky_method => "foobar!") }
- its(:system) { should == false }
- its(:link) { should == "http://example.com" }
- its(:sleep) { should == -5 }
- its(:format) { should == "Great" }
- its(:y) { should == 12345 }
- its(:more_format) { should == "format: Great" }
+ its(:system) { should == false }
+ its(:link) { should == "http://example.com" }
+ its(:sleep) { should == -5 }
+ its(:format) { should == "Great" }
+ its(:y) { should == 12345 }
+ its(:more_format) { should == "format: Great" }
+ its(:some_funky_method) { should == "foobar!" }
end

0 comments on commit d4045fe

Please sign in to comment.