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
Comments:
<< Home
alias_method_chain would also (theoretically) work, but it would leave me with artifacts (methods that I would never call in isolation).
I think the alias_method_chain solution is more conceptually easy to follow though, so you could make an argument for it.
I think the alias_method_chain solution is more conceptually easy to follow though, so you could make an argument for it.
Well, this seems to work well for you, although it doesn't seem as though you've escaped the lambda problem (calling bind(self) gives you a lambda, after all).
I had the same problem when using define_method to define instance methods on a class. I had to compromise. My original method looked something like this:
def include_month?(month)
self.each_as_time do |time|
return true if time.month == month
end
false
end
The compromise looked something like this:
sym = :month
define_method "include_#{sym}?" do |month|
truth_value = false
self.each_as_time do |time|
truth_value = true if time.month == month
end
truth_value
end
Makes for ugly code, IMHO. Anyway, thanks for the tip.
BTW, Where is alias_chain_method method defined?
Post a Comment
I had the same problem when using define_method to define instance methods on a class. I had to compromise. My original method looked something like this:
def include_month?(month)
self.each_as_time do |time|
return true if time.month == month
end
false
end
The compromise looked something like this:
sym = :month
define_method "include_#{sym}?" do |month|
truth_value = false
self.each_as_time do |time|
truth_value = true if time.month == month
end
truth_value
end
Makes for ugly code, IMHO. Anyway, thanks for the tip.
BTW, Where is alias_chain_method method defined?
<< Home




