Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何通过包含模块来包装Ruby方法的调用?_Ruby_Methods_Module - Fatal编程技术网

如何通过包含模块来包装Ruby方法的调用?

如何通过包含模块来包装Ruby方法的调用?,ruby,methods,module,Ruby,Methods,Module,我希望在我的某些课程中发生某些事情时得到通知。我想以这样一种方式设置它,即我的方法在这些类中的实现不会改变 我想我应该有如下模块: module Notifications extend ActiveSupport::Concern module ClassMethods def notify_when(method) puts "the #{method} method was called!" # additional suitable notifi

我希望在我的某些课程中发生某些事情时得到通知。我想以这样一种方式设置它,即我的方法在这些类中的实现不会改变

我想我应该有如下模块:

module Notifications
  extend ActiveSupport::Concern

  module ClassMethods
    def notify_when(method)
      puts "the #{method} method was called!"
      # additional suitable notification code
      # now, run the method indicated by the `method` argument
    end
  end
end
然后我可以像这样把它混合到我的课堂中:

class Foo
  include Notifications

  # notify that we're running :bar, then run bar
  notify_when :bar

  def bar(...)  # bar may have any arbitrary signature
    # ...
  end
end
我的主要愿望是,我不想修改
:bar
,以使通知正常工作。这能做到吗?如果是这样,我将如何编写
实现时的
通知_

另外,我使用的是Rails3,所以如果有ActiveSupport或其他技术我可以使用,请随意分享。(我查看了,但这需要我修改
bar
方法。)



我注意到我可能想使用“模块+超级技巧”。我不确定这是什么——也许有人能启发我?

我想你可以使用别名方法链

大概是这样的:

def notify_when(method)  
  alias_method "#{method}_without_notification", method
  define_method method do |*args|
    puts "#{method} called"
    send "#{method}_without_notification", args
  end
end

您不必自己用这种方法修改方法。

我可以想到两种方法:

(1) 修饰
Foo
方法以包含通知

(2) 使用代理对象拦截对
Foo
的方法调用,并在调用发生时通知您

第一种解决方案是Jakub采取的方法,尽管
alias_方法
不是实现这一点的最佳方法,但请改用此方法:

def notify_when(meth)  
  orig_meth = instance_method(meth)
  define_method(meth) do |*args, &block|
    puts "#{meth} called"
    orig_meth.bind(self).call *args, &block
  end
end
第二种解决方案要求您将
方法\u missing
与代理结合使用:

class Interceptor
  def initialize(target)
    @target = target
  end

  def method_missing(name, *args, &block)
    if @target.respond_to?(name)
      puts "about to run #{name}"
      @target.send(name, *args, &block)
    else
      super
    end
  end
end

class Hello; def hello; puts "hello!"; end; end

i = Interceptor.new(Hello.new)
i.hello #=> "about to run hello"
        #=> "hello!"

第一种方法需要修改方法(你说你不想要的),第二种方法需要使用代理,可能是你不想要的。很抱歉,没有简单的解决方案。

这个问题已经有很长一段时间没有解决了,但是还有一种可能是通过包含(或扩展)模块包装方法

由于2.0,您可以创建一个模块,有效地使其成为前置类的代理

在下面的示例中,调用扩展模块的方法,传递要包装的方法的名称。对于每个方法名称,将创建一个新模块并在其前面加上前缀。这是为了代码的简单性。您还可以将多个方法附加到单个代理

使用
alias_方法
instance_方法
的解决方案的一个重要区别是,您可以在定义方法本身之前定义要包装的方法

module Prepender

  def wrap_me(*method_names)
    method_names.each do |m|
      proxy = Module.new do
        define_method(m) do |*args|
          puts "the method '#{m}' is about to be called"
          super *args
        end
      end
      self.prepend proxy
    end
  end
end
使用:


如果:方法需要一个块怎么办?您不必使用
define\u method
,您可以执行
eval
来定义方法。例如,
eval“def#{method}(*args,&block).
这要求在方法定义之后出现
notify_,因此它对代码示例不起作用。它也会修改
bar
方法。但除了使用代理对象,还必须修改(装饰)您的方法。请您添加一个解释,为什么重新绑定原始方法比使用alias_方法更好?区别对我来说并不明显。谢谢。不过,我发现了。您的Prepender示例不必要地为每个包装的方法名称创建/预编一个模块;在sin上定义所有方法可能更有意义这是我在回答中所说的-第三段-代码简单性的次优示例。很抱歉,我没有仔细阅读解释。也就是说,我不确定为了“简单性”而保留代码原样是否有意义“。这不是很简单,只是很奇怪。你能提供没有多个模块的工作示例吗?这对我来说似乎不管用me@FilipBartuzi如果将
proxy=Module.new
移动到
method\u name之前,则应该可以工作。每个
,将
self.prepend proxy
移动到后面,并说
proxy.define\u method
而不是仅仅
define\u method
。那么您只需要制作一个代理模块。
class Dogbert
  extend Prepender

  wrap_me :bark, :deny

  def bark
    puts 'Bah!'
  end

  def deny
    puts 'You have no proof!'
  end
end

Dogbert.new.deny

# => the method 'deny' is about to be called
# => You have no proof!