Ruby 如何在包含的类中包含重新定义的方法

Ruby 如何在包含的类中包含重新定义的方法,ruby,Ruby,我写了这个模块: module Hooks module ExecutionHooks def before_action(hook, *method_names) method_names.each do |method_name| method = method(method_name) define_singleton_method(method_name) do |*args, &block| metho

我写了这个模块:

module Hooks
  module ExecutionHooks
    def before_action(hook, *method_names)
      method_names.each do |method_name|
        method = method(method_name)
        define_singleton_method(method_name) do |*args, &block|
          method(hook).call(*args)
          method.call(*args, &block)
        end
      end
    end
  end

  def self.included(base)
    base.send(:extend, Hooks::ExecutionHooks)
  end
end
此模块允许其他模块或类定义一个钩子,该钩子应在特定操作之前调用,类似于Rails中的
before_action
。 然后我将此模块包括在我的HTTParty模块中:

module HTTParty
  include Hooks
  before_action :perform_action, :get

  def self.perform_action
    puts "performed"
  end
end
class TestClient 
  include HTTParty
  ...
end
有一个类包括HTTParty模块:

module HTTParty
  include Hooks
  before_action :perform_action, :get

  def self.perform_action
    puts "performed"
  end
end
class TestClient 
  include HTTParty
  ...
end
当我尝试访问TestClient中的
get
方法时,它不会调用
perform\u action
。此处包含的
get
方法是原始方法,而不是重新定义的方法


有没有办法在TestClient类中包含重新定义的
get
方法?

您的代码几乎可以正常工作,但是
get
实际上并没有直接在
HTTParty
上定义,这是您没有想到的,而
HTTParty
included
类方法通过另一个路径将
get
添加到类中

HTTParty
有一个名为
HTTParty::ClassMethods
的模块,该模块包含
get
等。它将它们放在两个位置:在
HTTParty
本身上,您可以通过
include
钩子调用
HTTParty.get
,在任何带有
include HTTParty
的类上。当您打开
模块HTTParty
包含钩子
时,您正在
HTTParty.get
上插入钩子,这是调用
TestClient.get
时使用的另一个查找链。让您的
Hooks::ExecutionHooks
模块单独使用,我建议您制作一个
hookedhtparty
模块,而不是monkeypatching
HTTParty
。这将使事情变得更加清楚,并避免了
HTTParty
内部的复杂性,这是我们不应该真正去处理的

# hooked_httparty.rb
require 'httparty'
require 'hooks'

module HookedHTTParty
  module ClassMethods
    def global_perform(*args)
      puts "Running global perform with args #{args.inspect}"
    end
  end

  def self.included(base)
    base.include(HTTParty)
    base.include(Hooks)
    base.extend(HookedHTTParty::ClassMethods)
    base.before_action :global_perform, :get
  end
end
这确保
HTTParty
Hooks
base
上可用,然后在每个
get
上使用
global\u-perform
钩子对其进行扩展。与初始代码的主要区别在于在
base
TestClient
)而不是
HTTParty
)上调用动作之前调用
,因此我们采用了正确的
get
方法。您还将注意到
global\u perform
接受
*args
,因为您在生成钩子时是这样调用它的

由于我们包含了
钩子
,您现在还可以在
TestClient
本身中访问
操作前
,因此您还可以定义更具体的
操作前
s:

class TestClient
  include HookedHTTParty

  before_action :local_perform, :get

  def self.local_perform(*args)
    puts "Running local perform with args #{args.inspect}"
  end
end
运行
get
如下所示:

>TestClient.get'https://www.stackoverflow.com'
使用args运行本地执行[”https://www.stackoverflow.com"]
使用args运行全局执行[”https://www.stackoverflow.com"]
=> #

如果您真的需要任何东西,包括
HTTParty
来获取钩子(可能是因为您无法控制包含它的东西),那么您可能需要直接使用monkeypatch
HTTParty::ClassMethods
,因为这是定义
get
的瓶颈,但这正进入更黑暗的领域。只要你在周围注入代码,你也可以
其他东西。包括(hookedhtparty)
,使它更明确,并使它更封装。

我认为你的代码看起来非常接近目标,但更现代的方法是将
super
模块#prepend
一起使用。我对Ruby很陌生。请解释一下,我应该在什么时候使用模块#prepend?请阅读
prepend
文档,必要时进一步研究和实验,然后再要求我们编写更多文档。对不起,我忘了提到我正在使用的ruby版本。它是2.0.0p247。我对
模块#prepend
做了一些研究。我还不完全清楚,但看起来这正是我需要的。问题是,
prepend
在这个版本的ruby中是私有的。谢谢你澄清了所有的疑问。我正在从Java迁移到Ruby,因此需要一些时间来理解这两种语言在行为上的差异。