Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby catching NoMethodError and continue execution from where exception occurred

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.

like image 306
Overclocked Avatar asked Jan 28 '12 16:01

Overclocked


1 Answers

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
like image 114
Aliaksei Kliuchnikau Avatar answered Sep 30 '22 18:09

Aliaksei Kliuchnikau