Monday, March 12, 2007

Ruby: LocalJumpError workaround

I was recently working on some code very similar to the following distilled example.
class Foo

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
Executing the above code gives the following error.
LocalJumpError: unexpected return
The 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
Post a Comment