2017-03-22 1 views
3

J'ai des classes avec des méthodes qui se connectent lorsqu'une méthode est entré et sorti comme ceci:
def methodName1(args) @logger.debug(">>#{callee}") ... @logger.debug("<<#{callee}") end
utilisation metaprogramming pour entourer les méthodes dans une classe

def methodName2(args) @logger.debug(">>#{callee}") ... @logger.debug("<<#{callee}") end

Je me demandais s'il y avait un moyen de métaprogrammation pour entourer les méthodes avec l'enregistreur appels? Cela impliquerait d'identifier toutes les méthodes d'une classe que je veux entourer d'abord et ensuite les entourer.

A

Répondre

1

Je suppose que cette solution devrait aider:

class Foo 
    def initialize 
     (self.methods - Object.methods).each do |method| 
      # we need to make alias for method 
      self.class.send(:alias_method, "#{method}_without_callback", method) 
      # save method params, and destroy old method 
      params = self.method(method).parameters.map(&:last).join(',') 
      self.class.send(:undef_method, method) 
      # creating new method with old name, and pass params to this 
      eval(" 
      self.class.send(:define_method, method) do |#{params}| 
       puts 'Call_before' 
       self.send('#{method}_without_callback', #{params}) 
       puts 'Call_after' 
      end 
      ") 
     end 
    end 
    def say_without_param 
     puts "Hello!" 
    end 
    def say_hi(par1) 
     puts "Hi, #{par1}" 
    end 

    def say_good_bye(par1, par2) 
     puts "Good bye, #{par1} #{par2}" 
    end 
end 

Ainsi, lorsque nous créons un objet, après l'initialisation, il sera créé des méthodes d'alias, détruit les anciennes méthodes et de nouvelles méthodes avec call_backs seront créés;

Exemple d'utilisation:

obj = Foo.new 

obj.say_without_param # => Call_before 
          Hello! 
          Call_after 

obj.say_hi('Johny') # => Call_before 
          Hi, Johny 
          Call_after 

obj.say_good_bye('mr.', 'Smith') => Call_before 
            Good bye, mr. Smith 
            Call_after 
+0

exactement ce dont j'avais besoin. THX – amadain

1

Vous pouvez utiliser un alias autour. Alias ​​la méthode originale, puis redéfinir avec le code supplémentaire:

alias_method :bare_methodname1, :methodname1 

def methodname1(*args) 
    @logger.debug(">>#{callee}") 
    result = bare_methodname1(*args) 
    @logger.debug("<<#{callee}") 
    result 
end 

Ce n'est pas très différent de ce que vous avez maintenant, mais quand vous le combinez avec un tableau de noms de méthode, vous obtenez plus de ce que vous voulez:

method_names_ary.each do |name| 
    alias_method "bare_" + name, name 
    define_method(name) do |*args| 
    @logger.debug(">>#{callee}") 
    result = send("bare_" + name, *args) 
    @logger.debug("<<#{callee}") 
    result 
    end 
end 

Mettre cela dans la classe cible en dehors de toute méthode et il devrait redéfinir toutes les méthodes de votre choix pour avoir le code supplémentaire que vous voulez.

2

je serais enclin à préfixer à la classe un module anonyme créé dynamiquement dont les méthodes utilisent par exemple super appeler une méthode d'instance de la classe du même nom, après l'impression d'un message d'entrée de méthode et avant d'imprimer un message de sortie de méthode.

Commençons par créer une classe avec deux méthodes d'instance, on passe un bloc lorsqu'il est impliqué.

class C 
    def mouse(nbr_mice, impairment) 
    puts "%d %s mice" % [nbr_mice, impairment] 
    end 
    def hubbard(*args) 
    puts yield args 
    end 
end 

C.ancestors 
    #=> [C, Object, Kernel, BasicObject] 
c = C.new 
c.mouse(3, 'blind') 
    # 3 blind mice 
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') } 
    # OLD MOTHER HUBBARD 

Maintenant, nous construisons une méthode qui crée le module anonyme et l'ajoute à la classe.

def loggem(klass, *methods_to_log) 
    log_mod = Module.new do 
    code = methods_to_log.each_with_object('') { |m,str| str << 
     "def #{m}(*args); puts \"entering #{m}\"; super; puts \"leaving #{m}\"; end\n" } 
    class_eval code 
    end 
    klass.prepend(log_mod) 
end 

Nous sommes maintenant prêts à invoquer cette méthode avec les arguments correspondant à la classe à laquelle le module doit être préfixé et les méthodes d'instance de cette classe qui doivent être connectés.

loggem(C, :mouse, :hubbard) 

C.ancestors 
    #=> [#<Module:0x007fedab9ccf48>, C, Object, Kernel, BasicObject] 

c = C.new 
c.method(:mouse).owner 
    #=> #<Module:0x007fedab9ccf48> 
c.method(:mouse).super_method 
    #=> #<Method: Object(C)#mouse> 
c.method(:hubbard).owner 
    #=> #<Module:0x007fedab9ccf48> 
c.method(:hubbard).super_method 
    #=> #<Method: Object(C)#hubbard> 

c.mouse(3, 'blind') 
    # entering mouse 
    # 3 blind mice 
    # leaving mouse 
c.hubbard('old', 'mother', 'hubbard') { |a| a.map(&:upcase).join(' ') } 
    # entering hubbard 
    # OLD MOTHER HUBBARD 
    #leaving hubbard 

Voir Module::new et Module#prepend.

1

Vous pouvez créer une méthode de classe analogue à def qui ajoute les observateurs pour vous. Cela changerait un peu la syntaxe de définition de la méthode, mais pourrait rendre le code plus lisible.

-à-dire

module MethodLogging 
    def log_def(method_name, &definition) 
    define_method(method_name) do |*args| 
     @logger.debug(">>#{__callee__}") 
     definition.call(*args) 
     @logger.debug("<<#{__callee__}") 
    end 
    end 
end 

class MyClass 
    extend MethodLogging 

    def initialize 
    # make sure class has @logger defined, or else include it in some way in the MethodLogging module 
    @logger = Logger.new(STDOUT) 
    end 

    def regular_method(x) 
    puts x 
    end 

    log_def :logged_method do |x| 
    puts x 
    end 
end 

instance = MyClass.new 
instance.regular_method(3) 
# hello 
instance.logged_method(3) 
# D, [2017-03-22T14:59:18.889285 #58206] DEBUG -- : >>logged_method 
# world 
# D, [2017-03-22T14:59:18.889440 #58206] DEBUG -- : <<logged_method 

En dehors de la nouvelle syntaxe de définition de la méthode, il y a un inconvénient mineur où vous obtenez un comportement bizarre si vous ne respectez pas la arité de la méthode. Ni instance.logged_method() et instance.logged_method('hello', 'world') vont lancer une erreur avec cette méthode.