Ruby: How to capture and resolve undefined method when requiring a subclass file

Viewed 329

There is a DSL ruby file which I need to parse. It looks like

# file B.rb
class B < A
  hello "John"
end

hello "Mary"

It calls function hello() with a string parameter both inside and outside of the class B.

I try to require the file B.rb and resolve the missing methods:

# file A.rb

# define a class A for subclassing, before loading B.rb
class A
end

# Try to catch the missing methods
def method_missing(name, *args, &block)
  puts "tried to handle unknown method %s" % name # name is a symbol
end

require("./B.rb")

After running this code, the method_missing causes infinite loop.

How to capture and resolve undefined method both inside and outside of the class B? I want to know what the methods and parameters are called in the DSL file.

2 Answers

I hinted at this approach in my comment, but thought it might be useful to spell out.

So first of all we need to define the method_missing on something, we can't define it at the toplevel because we'll get this warning

redefining Object#method_missing may cause infinite loop

Instead, we'll define it on class A (at the class level).

# in a.rb

class A
  def self.method_missing(name, *args, &block)
    puts "tried to handle unknown method %s" % name # name is a symbol
  end
end

Now we can use class_eval to evaluate b.rb in the scope of class A like so:

# also in a.rb

A.class_eval(File.read("./b.rb"))

Now the "top level" hello "Mary" call is actually being executed in the class scope of A (where the method_missing definition is active). Furthermore, since class B inherits from A, it also has the method_missing definition on the class level, so the hello "John" call would hit it as well.

So running the script, I see tried to handle unknown method hello printed twice, as expected.

I should note, I don't think it's a good idea to just "ignore" errors in this way ... most likely it's better to halt the execution of the program. But perhaps you have something in mind that requires it, I can't know for sure.

This line in your method_missing,

puts "tried to handle unknown method %s" % name # name is a symbol

results in a call to to_ary which is not defined and winds up calling method_missing again. Hence your infinite loop.

def method_missing(name, *args, &block)
  p name
  puts "tried to handle unknown method %s" % name # name is a symbol
end
# =>
...
...
:to_ary
:to_ary
:to_ary
/System/Library/.../kernel_require.rb:55: stack level too deep (SystemStackError)

The fix is pretty simple if you want to do it this way. Use string interpolation.

puts "tried to handle unknown method #{name.to_s}" 
# =>
Tried to handle unknown method hello
Tried to handle unknown method hello
Related