Уровень стека слишком глубокий с method_added ruby - PullRequest
0 голосов
/ 26 ноября 2018

Я создал модуль для перехвата методов перед вызовом метода в классе:

module Hooks

def self.included(base)
 base.send :extend, ClassMethods
end


module ClassMethods
  # everytime we add a method to the class we check if we must redifine it
  def method_added(method)
    if @hooker_before.present? && @methods_to_hook_before.include?(method)
      hooked_method = instance_method(@hooker_before)
      @methods_to_hook_before.each do |method_name|
        begin
          method_to_hook = instance_method(method_name)
        rescue NameError => e
          return
        end
        define_method(method_name) do |*args, &block|
          hooked_method.bind(self).call
          method_to_hook.bind(self).(*args, &block) ## your old code in the method of the class
        end
      end
     end
   end

  def before(*methods_to_hooks, hookers)
   @methods_to_hook_before = methods_to_hooks
   @hooker_before = hookers[:call]
  end
 end
end

Я включил модуль в один из моих классов:

require_relative 'hooks'

class Block
  include Indentation
  include Hooks
  attr_accessor :file, :indent
  before :generate, call: :indent
  # after  :generate, call: :write_end

  def initialize(file, indent=nil)
    self.file = file
    self.indent = indent
  end

  def generate
    yield
  end
end

этот блокclass является родительским для другого класса, который реализует свою собственную версию метода generate и который фактически реализован.

Когда мой код работает, method_added фактически вызывается с методом: генерировать как аргумент в каком-то бесконечном цикле.Я не могу понять, почему method_added пойман в этом бесконечном цикле.Вы знаете, что не так с этим кодом?Вот ссылка на полный код: ссылка на код на github

1 Ответ

0 голосов
/ 26 ноября 2018

Вы вызвали бесконечную рекурсию, потому что вы вызываете define_method внутри method_added.Трассировка стека (которую вы, к сожалению, не предоставили) должна показать это.

Немного некрасивый обходной путь для решения этой проблемы может заключаться в явной установке переменной (например, @_adding_a_method) и использовании ее в качестве защитного предложения дляmethod_added:

module ClassMethods
  def method_added(method)
    return if @_adding_a_method

    if @hooker_before.present? && @methods_to_hook_before.include?(method)
      # ...

      @_adding_a_method = true
      define_method(method_name) do |*args, &block|
        # ...
      end
      @_adding_a_method = false

      # ...
    end
  end
end

Однако , делая шаг назад, я не совсем уверен, чего пытается достичь этот модуль.Не могли бы вы просто добиться этого с помощью Module#prepend вместо этого метапрограммирования?

Этот код напоминает мне о том, что вы можете найти в старом учебнике по Ruby 1.8 / 1.9 по расширенным мета-программам.методы программирования;Module#prepend делает такие обходные пути по большей части избыточными.

...