Defining “method_called”.. How do I make a hook method which gets called every time any function of a class gets called?

前端 未结 3 1328
北海茫月
北海茫月 2020-12-23 23:43

I want to make a hook method which gets called everytime any function of a class gets called. I have tried method_added, but it executes only once at the time of class defin

相关标签:
3条回答
  • 2020-12-23 23:52

    I recently wrote something that might be useful, though there are some provisos (see below). Here's the class you want to add your hook to:

    class Original  
      def regular_old_method msg
        puts msg
      end
    
    private
    
      def always_called method_called
        puts "'#{method_called.to_s.capitalize}' method's been called!"
      end
    end
    

    And here's the code for adding that hook:

    class << Original
      def new(*args)
        inner = self.allocate
        outer_name = self.name + 'Wrapper'
        outer_class = Class.new do
          def initialize inner_object
            @inner = inner_object
          end
          def method_missing method_called, *args
            @inner.send method_called, *args
            @inner.send :always_called, method_called
          end
        end
        outer_class_constant = Object.const_set(outer_name, outer_class)
        inner.send :initialize, *args
        outer_class_constant.new inner
      end
    end
    

    If you call it like this...

    o = Original.new
    o.regular_old_method "nothing unusual about this bit"
    

    You get the following output:

    nothing unusual about this bit

    'Regular_old_method' method's been called!

    This approach would be a bad idea if your code checked class names, because even though you've asked for an object of class 'Original', what you got back was an object of class 'OriginalWrapper'.

    Plus I imagine there could be other drawbacks to messing with the 'new' method, but my knowledge of Ruby metaprogramming doesn't stretch that far yet.

    0 讨论(0)
  • 2020-12-24 00:03

    Take a look at Kernel#set_trace_func. It lets you specify a proc which is invoked whenever an event (such as a method call) occurs. Here's an example:

    class Base
      def a
        puts "in method a"
      end
    
      def b
        puts "in method b"
      end
    end
    
    set_trace_func proc { |event, file, line, id, binding, classname|
      # only interested in events of type 'call' (Ruby method calls)
      # see the docs for set_trace_func for other supported event types
      puts "#{classname} #{id} called" if event == 'call'
    }
    
    b = Base.new
    b.a
    b.b
    

    Outputs:

    Base a called
    in method a
    Base b called
    in method b
    
    0 讨论(0)
  • 2020-12-24 00:09

    method_added is there to run code when a new method has been added to the class; it doesn't report when a method has been called. (As you discovered.)

    If you don't want to follow mikej's answer, here is a class that implements your specification:

    #!/usr/bin/ruby
    
    class Base
      def self.method_added(name)
        if /hook/.match(name.to_s) or method_defined?("#{name}_without_hook")
          return
        end
        hook = "def #{name}_hook\n p 'Method #{name} has been called'\n #{name}_without_hook\nend"
        self.class_eval(hook)
    
        a1 = "alias #{name}_without_hook #{name}"
        self.class_eval(a1)
    
        a2 = "alias #{name} #{name}_hook"
        self.class_eval(a2)
      end
      def a
        p "a called."
      end
      def b
        p "b called."
      end
    end
    t1 = Base.new
    t1.a
    t1.b
    t1.a
    t1.b
    

    And output:

    $ ./meta.rb
    "Method a has been called"
    "a called."
    "Method b has been called"
    "b called."
    "Method a has been called"
    "a called."
    "Method b has been called"
    "b called."
    
    0 讨论(0)
提交回复
热议问题