2013-02-28 5 views
4

От: http://cheind.blogspot.com/2008/12/method-hooks-in-ruby.htmlRuby: Как цеплять методы класса

Я имею

# 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 

Работа с любыми методами. Однако попытка подключиться к методам класса не делает ничего, что я получаю, потому что модуль NextHooks не реализует его. Как я могу реализовать перехватчики для методов класса? Я совершенно не знаю.

ответ

1

Вам необходимо указать код NextHook на Class и затем вызвать следующее, чтобы оно применимо к методам класса.

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

Edit:

Вот мой полный рабочий раствор, а затем это предложение:

# 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» будет меняться в одноэлементный контекст. Кроме того, если каким-то образом это было разрешено, оно все равно попадет в зацепку с вызовом «define method» в FollowHook. Поскольку define_method всегда определяет метод экземпляра, а не метод класса. Я думал об использовании метода «define_singleton_method», если контекст был классом, но это тоже не совсем так. –

+0

Вы запустили «Class.send (: include, FollowHook)»? Это то, что разрешает «неопределенный метод "в экземплярах класса. – 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

Это действительно работает сейчас :) –

+0

ДА! Оно делает. Я пробовал так много разных способов. Ничего не получилось. Это фантастика! Хотя я не уверен, что понимаю, как это сделать. В частности, что означает «metaclass = (класс << self, self, end)» do? –

+0

Поскольку все в ruby ​​- это объект, классы также являются объектами, поэтому они имеют класс, который их определяет. Поэтому термин metaclass: класс класса. И это способ получить метакласс. Проверьте это сообщение в блоге http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html –

Смежные вопросы