How do I wrap the invocation of a Ruby method by including a module?

前端 未结 3 1224
暖寄归人
暖寄归人 2020-12-29 13:52

I want to be notified when certain things happen in some of my classes. I want to set this up in such a way that the implementation of my methods in those classes doesn\'t c

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

    I imagine you could use an alias method chain.

    Something like this:

    def notify_when(method)  
      alias_method "#{method}_without_notification", method
      define_method method do |*args|
        puts "#{method} called"
        send "#{method}_without_notification", args
      end
    end
    

    You do not have to modify methods yourself with this approach.

    0 讨论(0)
  • 2020-12-29 14:30

    It has been quite a while since this question here has been active, but there is another possibility to wrap methods by an included (or extended) Module.

    Since 2.0 you can prepend a Module, effectively making it a proxy for the prepending class.

    In the example below, a method of an extended module module is called, passing the names of the methods you want to be wrapped. For each of the method names, a new Module is created and prepended. This is for code simplicity. You can also append multiple methods to a single proxy.

    An important difference to the solutions using alias_method and instance_method which is later bound on self is that you can define the methods to be wrapped before the methods themselves are defined.

    module Prepender
    
      def wrap_me(*method_names)
        method_names.each do |m|
          proxy = Module.new do
            define_method(m) do |*args|
              puts "the method '#{m}' is about to be called"
              super *args
            end
          end
          self.prepend proxy
        end
      end
    end
    

    Use:

    class Dogbert
      extend Prepender
    
      wrap_me :bark, :deny
    
      def bark
        puts 'Bah!'
      end
    
      def deny
        puts 'You have no proof!'
      end
    end
    
    Dogbert.new.deny
    
    # => the method 'deny' is about to be called
    # => You have no proof!
    
    0 讨论(0)
  • 2020-12-29 14:36

    I can think of two approaches:

    (1) Decorate the Foo methods to include a notification.

    (2) Use a proxy object that intercepts method calls to Foo and notifies you when they happen

    The first solution is the approach taken by Jakub, though the alias_method solution is not the best way to achieve this, use this instead:

    def notify_when(meth)  
      orig_meth = instance_method(meth)
      define_method(meth) do |*args, &block|
        puts "#{meth} called"
        orig_meth.bind(self).call *args, &block
      end
    end
    

    The second solution requires you to use method_missing in combination with a proxy:

    class Interceptor
      def initialize(target)
        @target = target
      end
    
      def method_missing(name, *args, &block)
        if @target.respond_to?(name)
          puts "about to run #{name}"
          @target.send(name, *args, &block)
        else
          super
        end
      end
    end
    
    class Hello; def hello; puts "hello!"; end; end
    
    i = Interceptor.new(Hello.new)
    i.hello #=> "about to run hello"
            #=> "hello!"
    

    The first method requires modifying the methods (something you said you didn't want) and the second method requires using a proxy, maybe something you do not want. There is no easy solution I'm sorry.

    0 讨论(0)
提交回复
热议问题