2011-07-08 1 views
7

Sto giocando con le funzioni di metaprogrammazione di Ruby e lo trovo un po 'peloso. Sto cercando di avvolgere una chiamata di metodo usando un modulo. Attualmente, sto facendo questo:Estensione di un metodo di classe in un modulo

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 

Tre domande:

  1. Esiste un modo per fare questo senza rinominare il metodo, in modo da consentire l'uso di super? O qualcosa di più pulito/più corto?

  2. C'è un modo pulito per impostare i valori predefiniti?

  3. Esiste un modo per spostare ulteriormente la chiamata wrap :bar?

+0

Nella vita reale userei l'ereditarietà di classe per quello. –

+0

Anch'io, ma sto cercando di dare un senso a tutto questo. :-) –

+0

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

risposta

6

1) Cleaner/più breve

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 

Per quanto ne so non c'è modo per raggiungere questo obiettivo senza rinominare. Potresti provare a chiamare super all'interno del blocco define_method. Ma prima di tutto, una chiamata a super all'interno di un define_method avrà esito positivo solo se si specificano argomenti esplicitamente, altrimenti si riceve un errore. Ma anche se chiami per es. super(*args), self in quel contesto sarebbe un'istanza di Foo. Quindi una chiamata a bar andrebbe alle super classi di Foo, non trovata e alla fine si tradurrebbe in un errore.

2) Sì, in questo modo

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

Tuttavia, in Ruby 1.8 è not possible utilizzare un blocco in define_method, ma questo è stato risolto per 1,9. Se si utilizza 1.9, è anche possibile utilizzare questo

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

3) No, sfortunatamente. alias_method richiede l'esistenza dei metodi che prende come input. Poiché i metodi di Ruby vengono creati mentre vengono analizzati, la chiamata wrap deve essere inserita dopo la definizione di bar altrimenti alias_method genererebbe un'eccezione.