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