Ruby catches NoMethodError and continues execution from which the exception occurred

In Ruby, I would like to catch a NoMethodError generated on an object in another object, then return some value to where the exception occurs and continue execution. Is there any 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 

Will print

 hello world 

Is there a better way to do this using Ruby's existing arrays of metaprogramming arsenal? Basically, I'm not sure if callcc will continue to exist.

Thanks.

+4
source share
2 answers

How 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 be printed

 hello cruel is undefined in both inner and outer classes world 

In this case, if the inner class does not define the required method, it delegates it to the outer class using Object # send . You can catch a NoMethodError inside method_missing to control when the Outer class does not define a 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 
+5
source

Ruby has a throw keyword that can be used to propagate errors up. I can’t say from your message that you would like to block this, but it looks something like this:

 class Outer catch :NoMethodError do #process the exception thrown from inner end class Inner def some_method ##...some other processing throw :NoMethodError if #.... #remaining statements end end end 

After the throw statement and catch block execute the rest of the instructions in some_method , execute

Hope this helps

+1
source

Source: https://habr.com/ru/post/1393526/


All Articles