2013-02-28 44 views
4

來源:http://cheind.blogspot.com/2008/12/method-hooks-in-ruby.html紅寶石:如何鉤住類方法

# Contains methods to hook method calls 
module FollowingHook 

    module ClassMethods 

    private 

    # Hook the provided instance methods so that the block 
    # is executed directly after the specified methods have 
    # been invoked. 
    # 
    def following(*syms, &block) 
     syms.each do |sym| # For each symbol 
     str_id = "__#{sym}__hooked__" 
     unless private_instance_methods.include?(str_id) 
      alias_method str_id, sym  # Backup original 
              # method 
      private str_id     # Make backup private 
      define_method sym do |*args| # Replace method 
      ret = __send__ str_id, *args # Invoke backup 
      block.call(self,    # Invoke hook 
       :method => sym, 
       :args => args, 
       :return => ret 
      ) 
      ret # Forward return value of method 
      end 
     end 
     end 
    end 
    end 

    # On inclusion, we extend the receiver by 
    # the defined class-methods. This is an ruby 
    # idiom for defining class methods within a module. 
    def FollowingHook.included(base) 
    base.extend(ClassMethods) 
    end 
end 

然後,我有一個類,像這樣:

class User 
    def self.get 
    #class method 
    end 
    def name 
    #instance method 
    end 
end 

在另一個位置/文件我重新打開用戶類別和掛鉤它

class User 
    include FollowingHooks # include the hook module 
    following :name do |receiver, args| 
    #do something. This works!! 
    end 
    following :get do |reciever, args| 
    #do something. THIS DOESNT WORK 
    # Which is to be expected looking at the FollowingHooks module definition. 
    end 
end 

掛鉤到任何實例方法的作品。然而,試圖鉤入類方法並沒有做任何事情,我得到了,因爲FollowingHooks模塊沒有實現它。我將如何實現類方法的鉤子?我完全無能爲力。

回答

1

您需要在Class上包含FollowingHook代碼,然後調用以下代碼以適用於類方法。

Class.send(:include, FollowingHook) 
class User 
    class << self 
    following :get do |reciever, args| 
     # Your awesome code here 
    end 
    end 
end 

編輯:

這是遵循了這一建議,我完全可行的解決方案:

# Contains methods to hook method calls 
module FollowingHook 

    module ClassMethods 

    private 

    # Hook the provided instance methods so that the block 
    # is executed directly after the specified methods have 
    # been invoked. 
    # 
    def following(*syms, &block) 
     syms.each do |sym| # For each symbol 
     str_id = "__#{sym}__hooked__" 
     unless private_instance_methods.include?(str_id) 
      alias_method str_id, sym  # Backup original 
              # method 
      private str_id     # Make backup private 
      define_method sym do |*args| # Replace method 
      ret = __send__ str_id, *args # Invoke backup 
      block.call(self,    # Invoke hook 
       :method => sym, 
       :args => args, 
       :return => ret 
      ) 
      ret # Forward return value of method 
      end 
     end 
     end 
    end 
    end 

    def self.included(base) 
    base.send(:extend, FollowingHook::ClassMethods) 
    end 
end 

class User 
    def self.foo 
    puts "foo" 
    end 
    def bar 
    puts "bar" 
    end 
end 

# You can put this in the class << self block if you prefer that the 
# methods only be available on the User class. 
Class.send(:include, FollowingHook) 

class User 
    include FollowingHook 
    following :bar do |receiver, args| 
    puts receiver.inspect 
    end 
    class << self 
    # If you prefer only the User class include FollowingHooks, use the 
    # following instead of including them in Class. 
    # include FollowingHook 
    following :foo do |receiver, args| 
     puts receiver.inspect 
    end 
    end 
end 

User.foo #=> 
# foo 
# User 
User.new.bar #=> 
# bar 
# #<User:0x338d9d> 
+0

這不起作用。我在#之後得到「未定義的方法」。這是有道理的,因爲「class << self'會改變爲單例上下文,並且如果以某種方式解決了這個問題,它仍然會影響到在FollowingHook中的」define method「調用,因爲define_method總是定義一個實例方法而不是如果上下文是一個類,我想過使用「define_singleton_method」,但是這也不起作用。 – 2013-03-01 00:46:28

+0

您是否運行了'Class.send(:include,FollowingHook)'?這就是解決了「undefined方法「的問題 – 2013-03-01 15:21:27

+0

一個類只是一個類的實例,所以在一個類的單例中調用'define_method'正確地將這些方法定義爲類方法 – 2013-03-01 15:23:10

1

嗯,這個工程。

def following_c(*syms, &block) 
     metaclass = (class << self; self; end) 
     syms.each do |sym| 
     str_id = "__#{sym}__hooked__" 
     unless metaclass.send(:private_instance_methods).include?(str_id) 
      metaclass.send :alias_method, str_id, sym   

      metaclass.send :private, str_id     
      metaclass.send :define_method, sym do |*args|  
      ret = send str_id, *args 
      block.call(self, 
       :method => sym, 
       :args => args, 
       :return => ret 
      ) 
      ret 
      end 
     end 
     end 
    end 
+0

現在真的有效:) – 2013-03-01 01:12:01

+0

是的!它的確如此。我一直在嘗試很多不同的方式。沒有工作。這是太棒了!雖然我不確定我的理解如何。具體來說,「metaclass =(class << self; self; end)」是做什麼的? – 2013-03-01 01:13:54

+0

因爲ruby中的所有東西都是對象,所以類也是對象,所以它們有一個定義它們的類。因此術語元類:一類的一類。這是獲得元類的一種方式。檢查這篇博文http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html – 2013-03-01 01:23:17