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.
# example 1
...
test_class.class_eval do
define_method :setup do
...
end
end
test_class.class_eval &block
...
An earlier version of this code only used one call to the class_eval method.
# example 2
...
test_class.class_eval do
define_method :setup do
...
end
instance_eval &block
end
...
Example 2 works when your controller tests contain only test methods.
# example 3
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
However, Example 2 stops working if your test class contains any helper methods.
# example 4
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
So, what's the difference between example 1 and example 2? The context in which the block is evaluated.

The above examples are from actual code, but the essence of the issue can be captured in a much simpler example.
# example 5
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>
As you can see in example 5, using def in an instance_eval is different from using def in a class_eval method call.

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 6
Foo = Class.new
foo = Foo.new
foo.instance_eval do
def instance_bar
"instance_bar"
end
end
foo.instance_bar #=> "instance_bar"
I've already explained where methods are stored in the various examples; however, the following example serves to prove my assertion.
# example 7
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"]
How does define_method fit into this picture?
# example 8
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"]
As 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.
# example 9
Foo.class_eval do
self #=> Foo
end

Foo.instance_eval do
self #=> Foo
end
By combining examples 5 and 8 I can create the following code that should no longer be surprising.
class Object
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"]
Thanks to Ali Aghareza, Pat Farley, and Zak Tamsen for collaborating with me on this.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.