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

发布于 2024-10-03 17:56:58 字数 1027 浏览 2 评论 0原文

当我的某些班级发生某些事情时,我希望收到通知。我想以这样的方式进行设置,使这些类中我的方法的实现不会改变。

我想我会有类似以下模块的东西:

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 来获取通知工作正常。这可以做到吗?如果是这样,我将如何编写 notify_when 实现?

另外,我正在使用 Rails 3,所以如果有我可以使用的 ActiveSupport 或其他技术,请随时分享。 (我查看了 ActiveSupport::Notifications,但这需要我修改 bar 方法。)


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

I want to be notified when certain things happen in some of my classes. I want to set this up in such a way that the implementation of my methods in those classes doesn't change.

I was thinking I'd have something like the following module:

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

Then I can mix it into my classes like so:

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

My key desire is that I don't want to have to modify :bar to get notifications working correctly. Can this be done? If so, how would I write the notify_when implementation?

Also, I'm using Rails 3, so if there are ActiveSupport or other techniques I can use, please feel free to share. (I looked at ActiveSupport::Notifications, but that would require me to modify the bar method.)


It has come to my attention that I might want to use "the Module+super trick". I'm not sure what this is -- perhaps someone can enlighten me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

野侃 2024-10-10 17:56:58

自从这里的这个问题一直活跃以来已经有一段时间了,但是还有另一种可能性通过包含(或扩展)的模块来包装方法。

从 2.0 开始,您可以前置模块,有效地使其成为前置类的代理。

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

与使用 alias_methodinstance_method(稍后绑定到 self)的解决方案的一个重要区别是,您可以在方法之前定义要包装的方法本身被定义。

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

使用:

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!

It has been quite a while since this question here has been active, but there is another possibility to wrap methods by an included (or extended) Module.

Since 2.0 you can prepend a Module, effectively making it a proxy for the prepending class.

In the example below, a method of an extended module module is called, passing the names of the methods you want to be wrapped. For each of the method names, a new Module is created and prepended. This is for code simplicity. You can also append multiple methods to a single proxy.

An important difference to the solutions using alias_method and instance_method which is later bound on self is that you can define the methods to be wrapped before the methods themselves are defined.

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

Use:

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!
把时间冻结 2024-10-10 17:56:58

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

像这样的事情:

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

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

I imagine you could use an alias method chain.

Something like this:

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

You do not have to modify methods yourself with this approach.

时光暖心i 2024-10-10 17:56:58

我可以想到两种方法:

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

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

第一个解决方案是 Jakub 采取的方法,尽管 alias_method 解决方案不是实现此目的的最佳方法是使用此方法:

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

第二个解决方案要求您将 method_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!"

第一个方法需要修改方法(您说您不想要的东西)和第二种方法需要使用代理,这也许是您不想要的。抱歉,没有简单的解决方案。

I can think of two approaches:

(1) Decorate the Foo methods to include a notification.

(2) Use a proxy object that intercepts method calls to Foo and notifies you when they happen

The first solution is the approach taken by Jakub, though the alias_method solution is not the best way to achieve this, use this instead:

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

The second solution requires you to use method_missing in combination with a proxy:

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!"

The first method requires modifying the methods (something you said you didn't want) and the second method requires using a proxy, maybe something you do not want. There is no easy solution I'm sorry.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文