2012-02-21 15 views
5

Volevo scrivere un po 'lib di "Deprecate-It" e ho usato molto la callback "method_added". Ma ora ho notato che questa richiamata non viene attivata, quando si include un modulo.Ruby method_added callback not trigger compresi i moduli

Esistono richiami o soluzioni alternative per informare la classe "Foobar" quando qualcosa è incluso in sé?

Piccolo Demo per dimostrare:

# Including Moduls won't trigger method_added callback 

module InvisibleMethod 
    def invisible 
    "You won't get a callback from me" 
    end 
end 

class Foobar 
    def self.method_added(m) 
    puts "InstanceMethod: '#{m}' added to '#{self}'" 
    end 

    def visible 
    "You will get a callback from me" 
    end 

    include InvisibleMethod 
end 

[:invisible, :visible, :wont_exist].each do |meth| 
    puts "#{meth}: #{Foobar.public_method_defined? meth}" 
end 

Questo è il risultato:

InstanceMethod: 'visible' added to 'Foobar' 
invisible: true 
visible: true 
wont_exist: false 

Informazioni aggiuntive:

ho davvero bisogno di usare un gancio come method_added.

ActiveModel sta aggiungendo public_instance_methods a Class durante il runtime tramite moduli anonimi.

+1

Purtroppo non penso ci sia una buona risposta, ma questo potrebbe indicarti la direzione di alcuni hack che funzionano .. http: // stackoverflow.it/questions/4191214/callback-for-classes-defined-inside-a-module – David

risposta

8

Il problema è che l'inclusione dei moduli non aggiunge metodi alle classi, ma modifica solo la catena di chiamata del metodo. Questa catena definisce quali classi/moduli saranno ricercati per un metodo, che non è definito per la classe in questione. Quello che succede quando includi un modulo è un'aggiunta di una voce in quella catena.

Questo è esattamente lo stesso di quando si aggiunge un metodo in una superclasse - questo non chiama method_added poiché non è definito nella superclasse. Sarebbe molto strano se una sottoclasse potesse cambiare il comportamento di una superclasse.

Si potrebbe rimediare chiamando manualmente il metodo aggiunto per un modulo incluso, ridefinendo include per la classe:

class Foobar 
    def self.include(included_module) 
    included_module.instance_methods.each{|m| self.method_added(m)} 
    super 
    end 
end 

Ed è molto più sicuro di ridefinire il metodo included in Module - il cambiamento è soltanto limitati alle classi, che hai definito te stesso.

+0

Il metodo method_added è privato di default, quindi se vuoi fare l'inverso di questo esempio e fare il lavoro all'interno del modulo incluso, dovrai fare: def self.included (base); base.send: metodo_added,: method_name; fine – odigity

3

Come suggerito da uno dei commenti, è possibile utilizzare un altro hook per ottenere il comportamento desiderato. Ad esempio, provare a aggiungere questo all'inizio del tuo codice:

class Module 
    def included(klass) 
    if klass.respond_to?(:method_added) 
     self.instance_methods.each do |method| 
     klass.method_added(method) 
     end 
    end 
    end 
end 

Ogni volta che un modulo è incluso in una classe, tutti i metodi di quel modulo di istanza saranno notificati alla classe, purché definisce il metodo method_added. Eseguendo il codice con il cambiamento di cui sopra ottengo questo risultato:

InstanceMethod: 'visible' added to 'Foobar' 
InstanceMethod: 'invisible' added to 'Foobar' 
invisible: true 
visible: true 
wont_exist: false 

Che credo sia il comportamento desiderato.

+0

oh, divertiti se qualcuno non chiama 'super' quando sovrascrive' included (klass) ' – Reactormonk

1

Penso che deprecation non sia un grosso problema per richiedere una libreria. è implementato in questo modo in datamapper. Informazioni sul gancio method_added; funziona come previsto perché i metodi sono già stati aggiunti a module non class. Solo tu puoi ottenere il tuo attacco previsto patch patching included.

# got from https://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/deprecate.rb 
module Deprecate 
    def deprecate(old_method, new_method) 
    class_eval <<-RUBY, __FILE__, __LINE__ + 1 
     def #{old_method}(*args, &block) 
     warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})" 
     send(#{new_method.inspect}, *args, &block) 
     end 
    RUBY 
    end 
end # module Deprecate 

class MyClass 
    extend Deprecate 

    def old_method 
    p "I am old" 
    end 
    deprecate :old_method, :new_method 

    def new_method 
    p "I am new" 
    end 
end 

m = MyClass.new 
m.old_method 

# MyClass#old_method is deprecated, use MyClass#new_method instead (pinger.rb:27:in `<main>') 
# "I am new" 
Problemi correlati