Monday, March 12, 2007
Ruby: LocalJumpError workaround
I was recently working on some code very similar to the following distilled example.
In my codebase the method_with_additional_behavior is defined in a superclass, and the subclasses use this method to create methods with additional behavior. I (believe I) could have forced them to use lambda and gotten around the problem, but that wouldn't be very friendly. I could also define another method, such as "__#{method_name}__", and call the underscored method from the desired method, but that would have left my classes with an additional method that should never be called in isolation.
I solved the problem by defining a method, storing that method in a local variable and then overriding the newly defined method with my additional behavior and a call to the method stored in the local variable.
class FooExecuting the above code gives the following error.
attr_accessor :some_val
def self.method_with_additional_behavior(method_name, &block)
define_method method_name do
# do some stuff before calling block
instance_eval &block
# do some stuff after calling block
end
end
method_with_additional_behavior :foo do
return 0 if self.some_val.nil?
some_val * 2
end
end
Foo.new.foo
LocalJumpError: unexpected returnThe error occurs because the
return statement attempts to return from the method_with_additional_behavior. There are a few ways to solve this, but I also had some constraints that influenced my decision.In my codebase the method_with_additional_behavior is defined in a superclass, and the subclasses use this method to create methods with additional behavior. I (believe I) could have forced them to use lambda and gotten around the problem, but that wouldn't be very friendly. I could also define another method, such as "__#{method_name}__", and call the underscored method from the desired method, but that would have left my classes with an additional method that should never be called in isolation.
I solved the problem by defining a method, storing that method in a local variable and then overriding the newly defined method with my additional behavior and a call to the method stored in the local variable.
class Foo
attr_accessor :some_val
def self.method_with_additional_behavior(method_name, &block)
define_method method_name, &block
new_method = instance_method(method_name)
define_method method_name do
# do some stuff before calling block
new_method.bind(self).call
# do some stuff after calling block
end
end
method_with_additional_behavior :foo do
return 0 if self.some_val.nil?
some_val * 2
end
end
Foo.new.foo #=> 0
Labels: block, define_method, instance_eval, instance_method, lambda, LocalJumpError, metaprogramming
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
Thursday, February 15, 2007
Ruby Evaluation options article on InfoQ
Yesterday, I published a fairly long article on InfoQ about Evaluation Options in Ruby. The article covers eval, instance_eval, class_eval and provides examples of using each of them.
Feedback welcome.
Feedback welcome.
Labels: class_eval, eval, InfoQ, instance_eval, ruby


