Friday, March 09, 2007
Ruby: instance_eval and class_eval method definitions
In yesterday's entry on Cleaning up Controller Tests you can find the following code.
The above examples are from actual code, but the essence of the issue can be captured in a much simpler example.
Example 5 shows that using def in a class_eval defines an instance method on the receiver (Foo in our example). However, using def in an instance_eval defines an instance method on the singleton class of the receiver (the singleton class of Foo in our example).
This explains why example 3 does not work: the valid_person method was being defined on the singleton class of the controller test class and not as an instance method of the controller test class.
As I previously stated, using def in an instance_eval defines an instance method on the singleton class of the receiver. This also explains how using def in an instance_eval when the receiver is an instance of a class defines a method on that instance.
# example 1An earlier version of this code only used one call to the class_eval method.
...
test_class.class_eval do
define_method :setup do
...
end
end
test_class.class_eval &block
...
# example 2Example 2 works when your controller tests contain only test methods.
...
test_class.class_eval do
define_method :setup do
...
end
instance_eval &block
end
...
# example 3However, Example 2 stops working if your test class contains any helper methods.
controller_tests do
test "should add the params" do
assert_equal 4, Math.add(2,2)
end
test "should multiply the params" do
assert_equal 9, Math.multiply(3,3)
end
end
# example 4So, what's the difference between example 1 and example 2? The context in which the block is evaluated.
controller_tests do
test "should not save if first name is nil" do
person = valid_person
person.first_name = nil
assert_equal false, person.save
end
def valid_person
Person.new(...)
end
end
The above examples are from actual code, but the essence of the issue can be captured in a much simpler example.
# example 5As you can see in example 5, using def in an instance_eval is different from using def in a class_eval method call.
Foo = Class.new
Foo.class_eval do
def bar
"bar"
end
end
Foo.instance_eval do
def baz
"baz"
end
end
Foo.bar #=> undefined method ‘bar’ for Foo:Class
Foo.new.bar #=> "bar"
Foo.baz #=> "baz"
Foo.new.baz #=> undefined method ‘baz’ for #<Foo:0x7dce8>
Example 5 shows that using def in a class_eval defines an instance method on the receiver (Foo in our example). However, using def in an instance_eval defines an instance method on the singleton class of the receiver (the singleton class of Foo in our example).
This explains why example 3 does not work: the valid_person method was being defined on the singleton class of the controller test class and not as an instance method of the controller test class.
As I previously stated, using def in an instance_eval defines an instance method on the singleton class of the receiver. This also explains how using def in an instance_eval when the receiver is an instance of a class defines a method on that instance.
# example 6I've already explained where methods are stored in the various examples; however, the following example serves to prove my assertion.
Foo = Class.new
foo = Foo.new
foo.instance_eval do
def instance_bar
"instance_bar"
end
end
foo.instance_bar #=> "instance_bar"
# example 7How does define_method fit into this picture?
class Object
def singleton_class
class << self; self; end
end
end
Foo = Class.new
Foo.class_eval do
def instance_method_of_Foo; end
end
Foo.instance_eval do
def instance_method_of_Foos_singleton_class; end
end
Foo.instance_methods(false).inspect
#=> ["instance_method_of_Foo"]
Foo.singleton_class.instance_methods(false).inspect
#=> ["instance_method_of_Foos_singleton_class", "new", "superclass", "allocate"]
foo = Foo.new
foo.instance_eval do
def instance_method_of_foos_singleton_class; end
end
foo.singleton_class.instance_methods(false).inspect
#=> ["instance_method_of_Foo", "instance_method_of_foos_singleton_class"]
# example 8As you can see from example 8 define_method always defines an instance method on the target (Foo in our example). This makes sense because define_method is being call on the implicit self, which evaluates to the receiver (Foo) in our example.
Foo = Class.new
Foo.class_eval do
define_method :instance_method_of_Foo do end
end
Foo.instance_eval do
define_method :another_instance_method_of_Foo do end
end
Foo.instance_methods(false).inspect
#=> ["another_instance_method_of_Foo", "instance_method_of_Foo"]
# example 9By combining examples 5 and 8 I can create the following code that should no longer be surprising.
Foo.class_eval do
self #=> Foo
end
Foo.instance_eval do
self #=> Foo
end
class ObjectThanks to Ali Aghareza, Pat Farley, and Zak Tamsen for collaborating with me on this.
def singleton_class
class << self; self; end
end
end
Foo = Class.new
Foo.instance_eval do
def an_instance_method_of_the_singleton_class
end
define_method :an_instance_method do
end
end
Foo.instance_methods(false).inspect
#=> ["an_instance_method"]
Foo.singleton_class.instance_methods(false).inspect
#=> ["an_instance_method_of_the_singleton_class", "new", "superclass", "allocate"]
Labels: class_eval, def, define_method, instance_eval, metaprogramming, ruby


