动态地向Ruby中的模块添加方法

时间:2018-06-06 16:23:40

标签: ruby metaprogramming

所以我有一堆看起来像这样的代码:

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我得到NoM​​ethodError,这是有道理的,因为我们在模块构建时调用detect并且模块(类?)无法调用它没有构建时自己的方法(对吧?)。

  • 如果我将它添加到另一个模块并将该模块包含在此模块中,我会收到相同的错误。

  • 我见过self.class.send(:define_method, method_name, method_definition)的代码,但我认为我没有达到足够的效果。

  • 也许有一种方法可以通过元类...来实现这一点。没看到如何为模块做这件事。 HM。

有没有合理的方法来做我想做的事情?

2 个答案:

答案 0 :(得分:1)

试试这个:

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

答案 1 :(得分:0)

在对该问题的评论中,OP清楚地表明所创建的所有方法都将执行相同的操作并返回相同的值。这意味着在第一个之后创建的所有实例方法都可以简单地成为原始实例方法的 aliases 。 (目前尚不清楚为什么这样做会有所帮助,但这不是重点。)在模块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

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