2013-06-27 183 views
0

有人可以幫我修改爲intercepting instance method calls提供的答案,以便它可以與類方法調用或類和實例方法調用一起使用?從我對Ruby元編程知識的有限知識來看,我會想象它會在使用class << self打開某個地方的單例類時做些什麼,但我已經嘗試過在不同的地方使用此代碼來做這件事,而且我似乎無法想辦法。然而,不是直接的答案,你能否向我提供正確的方向?除非我完全沒有深度,否則我很樂意爲自己做些事情。謝謝!如何攔截類方法調用,而不僅僅是實例方法調用

+0

您是在正確的策略。嘗試將你的'class << self'代碼放入類定義本身。 – Linuxios

回答

2

這是我的解決方案從您提供的鏈接中的答案修改而來。我將鉤子邏輯從超類移動到一個單獨的模塊,以便當有類需要鉤子時,它只包含或擴展該模塊並調用鉤子方法。

  • before_each_method type, &block - type可以:class:instance,以及該塊是每種方法之前要執行的代碼。該塊將在某些環境下進行評估,也就是說,方法self中的塊是該實例;對於類方法,塊中的self是類。
  • before_class_method &block - 別名before_each_method :class, &block
  • before_instance_method &block - 別名before_each_method :instance, &block
module MethodHooker 
    def self.included(base) 
    base.extend(ClassMethods) 
    end 
    def self.extended(base) 
    base.extend(ClassMethods) 
    end 
    module ClassMethods 
    def before_each_method type, &block 
     singleton = class << self; self; end 
     case type 
     when :instance 
     this = self 
     singleton.instance_eval do 
      define_method :method_added do |name| 
      last = instance_variable_get(:@__last_methods_added) 
      return if last and last.include?(name) 
      with = :"#{name}_with_before_each_method" 
      without = :"#{name}_without_before_each_method" 
      instance_variable_set(:@__last_methods_added, [name, with, without]) 
      this.class_eval do 
       define_method with do |*args, &blk| 
       instance_exec(name, args, blk, &block) 
       send without, *args, &blk 
       end 
       alias_method without, name 
       alias_method name, with 
      end 
      instance_variable_set(:@__last_methods_added, nil) 
      end 
     end 
     when :class 
     this = self 
     singleton.instance_eval do 
      define_method :singleton_method_added do |name| 
      return if name == :singleton_method_added 
      last = instance_variable_get(:@__last_singleton_methods_added) 
      return if last and last.include?(name) 
      with = :"#{name}_with_before_each_method" 
      without = :"#{name}_without_before_each_method" 
      instance_variable_set(:@__last_singleton_methods_added, [name, with, without]) 
      singleton.class_eval do 
       define_method with do |*args, &blk| 
       instance_exec(name, args, blk, &block) 
       send without, *args, &blk 
       end 
       alias_method without, name 
       alias_method name, with 
      end 
      instance_variable_set(:@__last_singleton_methods_added, nil) 
      end 
     end 
     end 
    end 
    def before_class_method &block 
     before_each_method :class, &block 
    end 
    def before_instance_method &block 
     before_each_method :instance, &block 
    end 
    end 
end 

class Test 
    extend MethodHooker 
    before_each_method :instance do |method, args, block| 
    p [method, args, block] 
    puts "before instance method(#{method}) #{@var}" 
    end 
    before_class_method do |method, args, block| 
    puts "before class method(#{method}) #{@class_instance_var}" 
    end 
    @class_instance_var = 'stackoverflow' 
    def initialize 
    @var = 1 
    end 
    def test(a, b, c) 
    puts "instance method test" 
    end 
    def self.test1 
    puts "class method test" 
    end 
end 

Test.new.test(1, "arg2", [3]) {|t| t} 
Test.test1 

的輸出將是這樣的:

[:initialize, [], nil] 
before instance method(initialize) 
[:test, [1, "arg2", [3]], #<Proc:[email protected]/Users/test/before_method.rb:88>] 
before instance method(test) 1 
instance method test 
before class method(test1) stackoverflow 
class method test 
+0

這很酷!我試圖在使用它之前消化所有這些,雖然哈哈。你可以向我解釋一下這個代碼片段在做什麼(在'when:instance'塊內): 'define_method with do | * args,&blk | instance_exec(name,args,blk,&block) 發送時沒有,* args,&blk end' – DesAdams

+0

具體使用'instance_exec()'。我不確定那是幹什麼的。 – DesAdams

+0

我想我明白了!這段代碼就是發生'rerouting'的地方:對'instance_exec()'的調用將原來的參數(給予原始方法調用)傳遞給給'before_each_method'的塊,然後執行該塊;之後它將發送執行原始方法調用與正確的參數。這是對的嗎? – DesAdams