2015-12-21 144 views
2

我写了这个模块:与类如何包括重新定义的方法包括

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 

有一类,其中包括了HTTParty模块:

class TestClient 
    include HTTParty 
    ... 
end 

当我尝试访问TestClient的的get方法,它不致电perform_action。这里包含的get方法是原始方法,而不是重新定义的方法。

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

+1

我认为你的代码看起来非常接近目标,但更现代的方法是在'Module#prepend'中使用'super'。 – sawa

+0

我对Ruby很新。请解释一下,我应该在代码的哪一点使用Module#prepend? – crashstorm

+0

请先阅读'prepend'文档,如果需要进行进一步研究并进行实验,然后再要求我们编写更多文档。 –

回答

1

您的代码几乎工作,但get实际上并没有直接定义在HTTParty,你没想到的,和HTTPartyincluded类方法通过其他路径添加到get类。

HTTParty有一个名为HTTParty::ClassMethods模块包含get等,这使他们在两个地方:在HTTParty本身,所以你可以打电话HTTParty.get,并在任何类include HTTParty,通过included钩。当您打开module HTTPartyinclude Hooks时,您将在HTTParty.get上插入挂钩,这是与您拨打TestClient.get时不同的查找链。离开您的Hooks::ExecutionHooks模块,我建议您制作一个HookedHTTParty模块,而不要使用单模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 

这可以确保HTTPartyHooks可在base,然后与global_perform钩上的每个get扩展它。与您的初始代码主要不同的是before_actionbaseTestClient)上被调用而不是在HTTParty上,所以我们赶上了正确的get方法。您还会注意到global_perform接受*args,因为您在生成挂钩时就是这样调用它的。

因为我们包括Hooks,你现在也有TestClient本身获得before_action,所以你也可以定义更加具体before_action 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' 
Running local perform with args ["https://www.stackoverflow.com"] 
Running global perform with args ["https://www.stackoverflow.com"] 
=> #<HTTParty::Response:0x7fa523a009e0 ... > 

如果你真的需要任何东西,包括HTTParty来获得你的钩子(可能是因为你没有包括它的东西的控制),你可能需要monkeypatch HTTParty::ClassMethods直接,因为这是定义get的瓶颈,但这是进入更黑暗的领域。只要你注入代码,你也可以使它更加明确,并保持它更多的封装。

+0

感谢您澄清所有的疑惑。我正在从Java迁移到Ruby,因此需要一些时间来理解这两种语言的行为差异。 – crashstorm