In Ruby, I'd like to catch the NoMethodError generated on an object in another object, then return some value to where the exception is raised and continue execution. Is there an existing way to do this?
The best I came up with is:
class Exception
attr_accessor :continuation
end
class Outer
def hello
puts "hello"
end
class Inner
def world
puts "world"
end
def method_missing(method, *args, &block)
x = callcc do |cc|
e = RuntimeError.exception(method)
e.continuation = cc
raise e
end
return x
end
end
def inner(&block)
inner = Inner.new
begin
inner.instance_eval(&block)
rescue => e
cc = e.continuation
cc.call(hello())
end
inner
end
end
o = Outer.new
o.inner do
hello
world
end
This prints
hello
world
Is there a better way to do this using Ruby's existing array of meta-programming arsenal? Basically, I am not sure if callcc will continue to exist.
Thanks.
What about this simple approach:
class Outer
def hello
puts "hello"
end
class Inner
def initialize outer
@outer = outer
end
def world
puts "world"
end
def method_missing(method, *args, &block)
@outer.send(method, *args, &block)
rescue NoMethodError # you can also add this
puts "#{method} is undefined in both inner and outer classes"
end
end
def inner(&block)
inner = Inner.new self
inner.instance_eval(&block)
inner
end
end
o = Outer.new
o.inner do
hello
cruel
world
end
Will print
hello
cruel is undefined in both inner and outer classes
world
In this case if inner class does not define required method it delegate it to the outer class using Object#send. You can catch NoMethodError
inside method_missing
in order to control situation when Outer
class does not define delegated method.
UPDATE You can also use fibers to solve the problem:
class Outer
def hello
puts "hello"
end
class Inner
def world
puts "world"
end
def method_missing(method, *args, &block)
Fiber.yield [method, args, block] # pass method args to outer
end
end
def inner(&block)
inner = Inner.new
f = Fiber.new { inner.instance_eval(&block) }
result = nil # result for first fiber call does not matter, it will be ignored
while (undef_method = f.resume result) # pass method execution result to inner
result = self.send(undef_method[0], *undef_method[1], &undef_method[2])
end
inner
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With