2010-07-07 3 views
5

Mes premières pensées sont quelque chose comme ceci:Comment exécuter du code avant et après une méthode dans une sous-classe?

class AbstractBuilder 
    attr_reader :time_taken 

    def build_with_timer 
    started_at = Time.now 
    build 
    @time_taken = Time.now - started_at 
    end 

    def build 
    raise 'Implement this method in a subclass' 
    end 
end 

class MyBuilder < AbstractBuilder 
    def build 
    sleep(5) 
    end 
end 

builder = MyBuilder.new.build_with_timer 
puts builder.time_taken 

Je pense qu'il ya une meilleure façon qui offre une meilleure flexibilité, par exemple idéalement, je voudrais appeler « construire » sur une instance de MyBuilder au lieu de 'build_with_timer' et ont toujours le temps d'exécution enregistré. J'ai envisagé d'utiliser alias_method depuis initialize ou même en utilisant un module mixin à la place de l'héritage de classe qui remplacerait la méthode de construction appelant super au milieu (je ne sais pas si cela fonctionnerait). Avant de descendre dans le trou du lapin, je pensais voir s'il y avait une pratique établie.

+0

Pour clarifier je veux définir une méthode, d'un nom connu, dans une sous-classe qui est enveloppée de façon transparente (avant et après) avec le code défini dans la classe de base. J'ai besoin de moins de flexibilité/de surcharge que les filtres ActiveRecord. – Kris

+0

https://github.com/PragTob/after_do – Kris

Répondre

2

Je joue avec alias_method:

module Timeable 
    def time_methods *meths 
    meths.each do |meth| 
     alias_method "old_#{meth}", meth 

     define_method meth do |*args| 
     started_at = Time.now 
     res = send "old_#{meth}", *args 
     puts "Execution took %f seconds" % (Time.now - started_at) 
     res 
     end 
    end 
    end 

end 

class Foo 
    def bar str 
    puts str 
    end 
end 

Foo.extend Timeable 
Foo.time_methods :bar 
Foo.new.bar('asd') 
#=>asd 
#=>Execution took 0.000050 seconds 
+0

Cela semble fonctionner, je ne veux pas avoir à appeler time_methods puisque dans mon cas c'est la seule et unique méthode de la sous-classe, toujours du même nom, que je veux emballer autour du code. Je vais essayer d'utiliser alias_method et define_method d'initialize dans la classe de base. Cela suppose que les méthodes de la sous-classe existent quand initialize est appelé?!? – Kris

+0

Vous m'avez perdu là-bas. Il serait utile que vous décriviez le vrai problème que vous essayez de résoudre. Pour commencer, avez-vous le contrôle de la classe parente? Pouvez-vous changer la méthode 'build_with_timer' ou pas? –

+0

Oui, je contrôle les deux classes, il y aura une convention d'avoir une méthode appelée 'build' dans les sous-classes, qui quand on appelle avec le code spécifié dans la classe de base avant et après la méthode, le code avant/après sera toujours le même. J'espère que cela a du sens. – Kris

0

On dirait que vous recherchez des accroches dans les événements du cycle de vie des objets. Vous devrez construire ceci dans votre objet de base et fournir un peu de DSL - je pense que vous cherchez quelque chose comme ActiveRecord Callbacks. Voici comment nous pourrions modifier votre exemple pour permettre à quelque chose comme ça:

class AbstractBuilder 
    attr_reader :time_taken 

    def construct! # i.e., build, and also call your hooks 
    @@prebuild.each { |sym| self.send(sym) } 
    build 
    @@postbuild.each { |sym| self.send(sym) } 
    end 

    def construct_with_timer 
    started_at = Time.now 
    construct! 
    @time_taken = Time.now - started_at 

    puts "!!! Build time: #@time_taken" 
    end 

    class << self 
    def before_build(fn); @@prebuild ||= []; @@prebuild << fn; end 
    def after_build(fn); @@postbuild ||= []; @@postbuild << fn; end 
    end 
end 

class MyBuilder < AbstractBuilder 
    before_build :preprocess 
    after_build :postprocess 

    def build; puts "BUILDING"; sleep(3); end 
    def preprocess; puts "Preparing to build..."; end 
    def postprocess; puts "Done building. Thank you for waiting."; end 
end 

builder = MyBuilder.new 
builder.construct_with_timer 

# => Preparing to build... 
# => BUILDING 
# => Done building. Thank you for waiting. 
# => !!! Build time: 3.000119 
+0

Oui et non, je veux le concept de filtres avant/après (ou autour) mais il n'y aura jamais qu'une seule méthode, toujours avec le même nom, que j'ai besoin envelopper, et il sera toujours enveloppé dans le même code. J'ai donc besoin d'une version codée en dur. De votre exemple, il semble que j'ai besoin de stocker le code avant/après au niveau de la classe, puis l'injecter après que l'objet a été instancié? – Kris

+0

Droit - la logique avant/après est un petit langage pour construire rapidement des classes enfants. Pour des exigences plus compliquées, notez que Ruby lui-même fournit également quelques rappels internes ici qui sont utiles. Vous pouvez écrire des méthodes nommées 'inherited' et 'included' et celles-ci seront déclenchées chaque fois que la classe est héritée ou incluse. –

4

J'ai eu un coup de couteau à une version pour obtenir ce que vous voulez. Cette version ne nécessite pas de sous-classe pour avoir du code supplémentaire non plus.

class AbstractBuilder 

    @@disable_override = false 

    def before_method 
    puts "before" 
    end 

    def after_method 
    puts "after" 
    end 

    def self.method_added name 
    unless @@disable_override 
     if name == :build 
     @@disable_override = true # to stop the new build method 
     self.send :alias_method, :sub_build, :build 
     self.send :remove_method, :build 
     self.send :define_method, :build do 
      before_method 
      sub_build 
      after_method 
     end 
     @@disable_override = false 
     else 
     puts "defining other method #{name}" 
     end 
    end 
    end 

end 

class MyBuilder < AbstractBuilder 

    def build 
    puts "starting build" 
    sleep(5) 
    puts "built." 
    end 

    def unnaffected_method 
    # this method won't get redefined 
    end 

end 

b = MyBuilder.new 
b.build 

Sorties

defining other method unnaffected_method 
before 
starting build 
built. 
after 
0

Ceci est une utilisation définition de manuel cas pour Aspect-Oriented Programming. Il offre généralement une séparation plus nette des préoccupations. Dans ce domaine, Ruby propose Aquarium et AspectR. Toutefois, vous pouvez ne pas vouloir ajouter une autre dépendance à votre projet. En tant que tel, vous pourriez toujours envisager d'utiliser l'une des autres approches.

Questions connexes