2010-07-07 8 views
5

Mis primeros pensamientos son algo como esto:¿Cómo ejecuto el código antes y después de un método en una subclase?

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 

Sospecho que hay una manera mejor que ofrece una mayor flexibilidad, por ejemplo Idealmente me gustaría llamar a 'construir' en una instancia de MyBuilder en lugar de 'build_with_timer' y siempre se registra el tiempo de ejecución.

Consideré usar alias_method desde inicializar o incluso usar un módulo mixin en lugar de herencia de clase que anularía el método de compilación al llamar a super en el medio (no estoy seguro si eso funcionaría). Antes de bajar por el agujero del conejo, pensé que vería si hay una práctica establecida.

+0

Para aclarar Quiero definir un método, de un nombre conocido, en una subclase que se envuelve de forma transparente (antes y después) con el código definido en la clase base. Necesito menos flexibilidad/sobrecarga que decir filtros ActiveRecord. – Kris

+0

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

Respuesta

2

jugaría con 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

Parece que podría funcionar, no quiero tener que llamar a time_methods ya que en mi caso el es un único método en la subclase, siempre del mismo nombre, y quiero ajustar el código. Intentaré usar alias_method y define_method desde initialize en la clase base. ¿Eso está suponiendo que los métodos en la subclase existen cuando se llama inicializar?!? – Kris

+0

Me has perdido allí. Ayudaría si describieras qué problema real estás tratando de resolver. Para empezar, ¿tienes el control de la clase de padres? ¿Puedes cambiar el método 'build_with_timer' o no? –

+0

Sí Yo controlo ambas clases, habrá una convención de tener un método llamado 'compilación' en las subclases, que cuando se llama con un código especificado en la clase base ejecutada antes y después del método, el código antes/después siempre será lo mismo. Espero que tenga sentido. – Kris

0

Parece que está buscando ganchos en los eventos del ciclo de vida de los objetos. Tendrás que construir esto en tu objeto base y proporcionar un pequeño DSL. Estoy pensando en algo como ActiveRecord Callbacks. He aquí cómo podríamos modificar ejemplo, para permitir algo así:

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

Sí y no, quiero el concepto de filtros antes/después (o alrededor) pero siempre habrá un solo método, siempre con el mismo nombre, que necesito para envolver, y siempre estará envuelto en el mismo código. Entonces necesito una versión codificada de esto. De su ejemplo, parece que necesito almacenar el código de antes/después en el nivel de clase, luego lo inyecte después de que el objeto ha sido instanciado. – Kris

+0

Derecha: la lógica de antes/después es un pequeño lenguaje para crear rápidamente clases secundarias. Para requisitos más complicados, tenga en cuenta que Ruby también proporciona algunas devoluciones de llamada internas que son útiles. Puede escribir métodos llamados 'heredados' e 'incluidos', que se activarán siempre que la clase se herede o se incluya. –

4

tuve una puñalada en una versión para lograr lo que quiere. Esta versión no requiere que la subclase tenga ningún código adicional tampoco.

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 

salidas

defining other method unnaffected_method 
before 
starting build 
built. 
after 
0

Este es un caso de uso de libros de texto para la definición Aspect-Oriented Programming. Por lo general, ofrece una separación más clara de las preocupaciones. En este campo, Ruby ofrece Aquarium y AspectR. Sin embargo, es posible que no desee agregar otra dependencia a su proyecto. Como tal, aún podría considerar usar uno de los otros enfoques.

Cuestiones relacionadas