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
Why not just use alias_method_chain, is there some magic in the block that I'm missing?
ReplyDeletealias_method_chain would also (theoretically) work, but it would leave me with artifacts (methods that I would never call in isolation).
ReplyDeleteI 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).
ReplyDeleteI 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?
Hi, I am new to ruby, but like lambda in other languages
ReplyDeleteHere is my approach
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
result = block.call(self)
p result
# do some stuff after calling block
end
end
method_with_additional_behavior :foo, lambda { |s|
return 0 if s.some_val.nil?
p s
#s.some_val * 2
}
end
Foo.new.foo