Ruby: как захватить и разрешить неопределенный метод, когда требуется файл подкласса

Есть рубиновый файл DSL, который мне нужно разобрать. Это выглядит как

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

hello "Mary"

Он вызывает функцию hello() со строковым параметром как внутри, так и вне класса B.

Я пытаюсь require файл B.rb и решить недостающие методы:

# 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")

После запуска этого кода method_missing вызывает бесконечный цикл.

Как захватить и разрешить неопределенный метод как внутри, так и вне класса B? Я хочу знать, как называются методы и параметры в файле DSL.

🤔 А знаете ли вы, что...
Ruby активно используется во множестве веб-сервисов и стартапов.


2
472
2

Ответы:

Решено

Я намекнул на этот подход в своем комментарии, но подумал, что было бы полезно разъяснить.

Итак, прежде всего нам нужно определить method_missing для чего-то, мы не можем определить это на верхнем уровне, потому что мы получим это предупреждение

переопределение Object#method_missing может вызвать бесконечный цикл

Вместо этого мы определим его на class A (на уровне класса).

# 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

Теперь мы можем использовать class_eval для оценки b.rb в рамках class A следующим образом:

# also in a.rb

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

Теперь вызов «верхнего уровня» hello "Mary" фактически выполняется в области класса A (где определение method_missing активно). Кроме того, поскольку class B наследуется от A, он также имеет определение method_missing на уровне класса, поэтому вызов hello "John" также поразит его.

Итак, запустив скрипт, я вижу, что tried to handle unknown method hello напечатано дважды, как и ожидалось.

Должен отметить, я не думаю, что это хорошая идея просто "игнорировать" ошибки таким образом... скорее всего, лучше остановить выполнение программы. Но, возможно, вы имеете в виду что-то, что требует этого, я не могу знать наверняка.


Эта строчка в твоем method_missing,

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

приводит к вызову to_ary, который не определен, и снова вызывает method_missing. Отсюда ваш бесконечный цикл.

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)

Исправление довольно простое, если вы хотите сделать это таким образом. Используйте интерполяцию строк.

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