2011-07-08 9 views
7

Estoy jugando con las funciones de metaprogramación de ruby, y me está resultando un poco complicado. Estoy tratando de ajustar una llamada a método usando un módulo. Actualmente, estoy haciendo esto:Ampliando un método de clase en un módulo

module Bar 
    module ClassMethods 
    def wrap(method) 
     class_eval do 
     old_method = "wrapped_#{method}".to_sym 
     unless respond_to? old_method 
      alias_method old_method, method 

      define_method method do |*args| 
      send old_method, *args 
      end 
     end 
     end 
    end 
    end 

    def self.included(base) 
    base.extend ClassMethods 
    end 
end 

class Foo 
    include Bar 

    def bar(arg = 'foo') 
    puts arg 
    end 

    wrap :bar 
end 

Tres preguntas:

  1. ¿Hay alguna manera de hacer esto sin cambiar el nombre del método, a fin de permitir el uso de super? O algo más limpio/más corto?

  2. ¿Hay una forma clara de establecer los valores predeterminados?

  3. ¿Hay algún medio para mover la llamada wrap :bar más arriba?

+0

En la vida real usaría la herencia de clase para eso. –

+0

Yo también, pero estoy tratando de darle sentido a todo esto. :-) –

+0

Relacionados: http://stackoverflow.com/questions/6631182/difference-between-class-eval-and-instance-eval-in-a-module –

Respuesta

6

1) Limpiador/más corto

module ClassMethods 
    def wrap(method) 
    old = "_#{method}".to_sym 
    alias_method old, method 
    define_method method do |*args| 
     send(old, *args) 
    end 
    end 
end 

class Foo 
    extend ClassMethods 

    def bar(arg = 'foo') 
    puts arg 
    end 

    wrap :bar 
end 

Por lo que yo sé que no hay manera de lograr esto sin cambiar el nombre. Puede intentar llamar al super dentro del bloque define_method. Pero antes que nada, una llamada al super desde un define_method solo tendrá éxito si especifica los argumentos explícitamente, de lo contrario recibirá un error. Pero incluso si llamas, por ejemplo super(*args), self en ese contexto sería una instancia de Foo. Por lo tanto, una llamada a bar iría a las súper clases de Foo, no se encontrará y finalmente dará como resultado un error.

2) Sí, al igual que

define_method method do |def_val='foo', *rest| 
    send(old, def_val, *rest) 
end 

Sin embargo, en Ruby 1.8 es not possible utilizar un bloque en define_method, pero esto se ha fijado para 1.9. Si está utilizando 1.9, también puede utilizar este

define_method method do |def_val='foo', *rest, &block| 
    send(old, def_val, *rest, &block) 
end 

3) No, desafortunadamente. alias_method requiere la existencia de los métodos que toma como entrada. A medida que los métodos de Ruby comienzan a existir a medida que se analizan, la llamada wrap se debe realizar después de la definición de bar, de lo contrario, alias_method generaría una excepción.

Cuestiones relacionadas