динамическое добавление метода в модуль в Ruby - PullRequest
0 голосов
/ 06 июня 2018

, поэтому у меня есть код, который выглядит следующим образом:

module Foo
  def detect(this_message)
    #check for timeout
    if Time.now > instance_variable_get("@#{this_message}_timeout".to_sym)
      @state_machine.method("#{this_message}_timed_out".to_sym).call
      return
    end
    yield
    record
  rescue StandardError => e
    # retry on exception
    @state_machine.method("#{this_message}_retry".to_sym).call(exception: e)
  end

  # a bunch of these
  def detect_blah
    detect(:blah) do
      # detection code
      @state_machine.method("#{this_message}_detected".to_sym).call
      # or failed, you get the idea
    end
  end
end

...
class Bar
  include Foo
  # more stuff
end

Я хочу исключить объявление def detect_blah.Я хочу просто сказать detect(:blah) и попросить его динамически добавить метод detect_blah, который включает в себя все те же обработки, что и выше, включая полученный блок.

Я пробовал несколько перестановок define_method.

  • Если я просто позвоню define_method из detect, я получу NoMethodError, что имеет смысл, потому что мы вызываем функцию обнаружения во время создания модуля, а модуль (класс?) Может 'не вызывать свои собственные методы, когда он не построен (верно?).

  • Если я добавлю его в другой модуль и включу этот модуль в этот, я получу ту же ошибку.

  • Я видел код, который делает self.class.send(:define_method, method_name, method_definition), но я не думаю, что получаю достаточно далеко, чтобы это сработало.

  • возможно, есть способ сделать это через метакласс ... для классов.Не видя, как это сделать для модуля.Хм.

Есть ли разумный способ сделать то, что я хочу сделать?

Ответы [ 2 ]

0 голосов
/ 07 июня 2018

В комментарии к вопросу ОП пояснил, что все созданные методы будут выполнять одни и те же операции и возвращать одинаковые значения.Это означает, что все методы экземпляра, созданные после первого, можно просто сделать псевдонимами исходного метода экземпляра.(Непонятно, почему это было бы полезно, но это не относится к делу.) В модуле Foo я назвал этот оригинальный мгновенный метод boilerplate.Мы могли бы написать следующее:

module Foo
  def detect(method_name)
    self.class.send(:alias_method, method_name, :boilerplate)
  end

  def boilerplate
    yield "Spud"
  end
end

class Bar
  include Foo
end

См. Модуль # alias_method .Нам нужно использовать send, потому что alias_method является частным методом.

Теперь мы можем написать следующее.

Bar.instance_methods && [:detect, :boilerplate]
  #=> [:detect, :boilerplate]

b = Bar.new
b.detect(:say)
b.detect("hey")

Bar.instance_methods(false)
  #=> [:say, :hey]

b.say { |name| "My name is #{name}" }
  #=> "My name is Spud"
b.hey { |name| "My name is #{name}" }
  #=> "My name is Spud"
0 голосов
/ 06 июня 2018

Попробуйте это:

module Foo
  def detect(this_message, &block)
    # boilerplate stuff using instance_variable_get(blah) and
    # calling methods on instance variables...
    yield block
    # more boilerplate stuff
    self.class.send(:define_method, "detect_#{this_message.to_s}") do
        puts "This is templated detection code for #{this_message.to_s}"
        # blah
    end
  end
end
...