2010-02-03 3 views
7

Sto cercando di ottenere una richiamata quando viene chiamato un metodo su una particolare classe. Ignorare "invia" non funziona. Sembra che send non venga chiamato nel normale richiamo del metodo Ruby. Prendi il seguente esempio.Sovrascrivere le chiamate di metodo in Ruby?

class Test 
    def self.items 
    @items ||= [] 
    end 
end 

Se ridefiniamo inviare il test, e quindi chiamare Test.items, Invia non viene chiamato.

È quello che sto cercando di fare possibile?

Preferisco non utilizzare set_trace_func, poiché probabilmente rallenterà notevolmente le cose.

+1

Potrebbe essere possibile farlo aliasando la classe alla mia classe e delegando le chiamate di metodo alla classe originale. –

+1

Protip: tutte le risposte che ti dicono come sovrascrivere un singolo metodo ti dicono anche come farlo con tutti i metodi implementati da una classe. Hai solo bisogno di scorrere su di loro. E 'questa la tua vera domanda? – Chuck

risposta

12

Usa alias o alias_method:

# the current implementation of Test, defined by someone else 
# and for that reason we might not be able to change it directly 
class Test 
    def self.items 
    @items ||= [] 
    end 
end 

# we open the class again, probably in a completely different 
# file from the definition above 
class Test 
    # open up the metaclass, methods defined within this block become 
    # class methods, just as if we had defined them with "def self.my_method" 
    class << self 
    # alias the old method as "old_items" 
    alias_method :old_items, :items 
    # redeclare the method -- this replaces the old items method, 
    # but that's ok since it is still available under it's alias "old_items" 
    def items 
     # do whatever you want 
     puts "items was called!" 
     # then call the old implementation (make sure to call it last if you rely 
     # on its return value) 
     old_items 
    end 
    end 
end 

ho riscritto il codice utilizzando la sintassi class << self di aprire il metaclasse, perché io non sono sicuro di come utilizzare alias_method sui metodi di classe diversa.

+2

No, desidero una richiamata per * qualsiasi * richiamo del metodo. Gli articoli sono solo un esempio. –

+0

Quindi fallo con tutti i tuoi metodi. – Chuck

+1

Combinato con ': method_added' dovrebbe fare quello che ti serve. – Theo

0

Non ho una risposta completa, ma sto pensando method_added potrebbe essere utile qui.

2

È possibile vedere come ciò avviene tramite la funzionalità di aggancio ExtLib. ExtLib::Hook consente fondamentalmente di invocare callback arbitrari prima o dopo il completamento di un metodo. Vedi il codice su GitHub qui per come è stato fatto (sostituisce :method_added per riscrivere automaticamente i metodi man mano che vengono aggiunti alla classe).

4

Qualcosa di simile a questo: funziona con metodi di istanza e metodi della classe, non sarà solo intercettare gli attuali metodi definiti nella classe, ma qualsiasi che vengono aggiunti in seguito se la riapertura della classe ecc

(c'è anche rcapture http://code.google.com/p/rcapture/):

module Interceptor 
    def intercept_callback(&block) 
    @callback = block 
    @old_methods = {} 
    end 
    def method_added(my_method) 
    redefine self, self, my_method, instance_method(my_method) 
    end 
    def singleton_method_added(my_method) 
    meta = class << self; self; end 
    redefine self, meta, my_method, method(my_method) 
    end 
    def redefine(klass, me, method_name, my_method) 
    return unless @old_methods and not @old_methods.include? method_name 
    @old_methods[method_name] = my_method 
    me.send :define_method, method_name do |*args| 
     callback = klass.instance_variable_get :@callback 
     orig_method = klass.instance_variable_get(:@old_methods)[method_name] 
     callback.call *args if callback 
     orig_method = orig_method.bind self if orig_method.is_a? UnboundMethod 
     orig_method.call *args 
    end 
    end 
end 

class Test 
    extend Interceptor 
    intercept_callback do |*args| 
    puts 'was called' 
    end 
    def self.items 
    puts "items" 
    end 
    def apple 
    puts "apples" 
    end 
end 

class Test 
    def rock 
    puts "rock" 
    end 
end 

Test.items 
Test.new.apple 
Test.new.rock 
+0

Ancora più utile se si passa 'method_name' e' caller' al callback prima o al posto di '* args', e quindi si dice' intercept_callback do | method_name, by_caller | ; mette "# {method_name} chiamato da # {by_caller.first}"; end' –

1

si può fare qualcosa di simile, si può anche mettere le condizioni sul metodo chiamato o no (non credo che sia di utile, ma ancora lo avete nel caso).

module MethodInterceptor 

    def self.included(base) 
    base.extend(ClassMethods) 
    base.send(:include, InstanceMethods) 
    base.class_eval do 
     # we declare the method_list on the class env 
     @_instance_method_list = base.instance_methods.inject(Hash.new) do |methods, method_name| 
     # we undef all methods 
     if !%w(__send__ __id__ method_missing class).include?(method_name) 
      methods[method_name.to_sym] = base.instance_method(method_name) 
      base.send(:undef_method, method_name) 
     end 
     methods 
     end 
    end 
    end 

    module ClassMethods 

    def _instance_method_list 
     @_instance_method_list 
    end 

    def method_added(name) 
     return if [:before_method, :method_missing].include?(name) 
     _instance_method_list[name] = self.instance_method(name) 
     self.send(:undef_method, name) 
     nil 
    end 

    end 

    module InstanceMethods 

    def before_method(method_name, *args) 
     # by defaults it always will be called 
     true 
    end 

    def method_missing(name, *args) 
     if self.class._instance_method_list.key?(name) 
     if before_method(name, *args) 
      self.class._instance_method_list[name].bind(self).call(*args) 
     else 
      super 
     end 
     else 
     super 
     end 
    end 
    end 

end 

class Say 
    include MethodInterceptor 

    def before_method(method_name, *args) 
    # you cannot say hello world! 
    return !(method_name == :say && args[0] == 'hello world') 
    end 

    def say(msg) 
    puts msg 
    end 

end 

Spero che questo funzioni.

0

Ho funzionato usando una classe Proxy e quindi impostando una costante utilizzando il nome della classe reale. Non sono sicuro di come farlo funzionare con le istanze però. C'è un modo per cambiare quali variabili oggetto stanno puntando troppo?

Fondamentalmente, io voglio fare questo:

t = Test.new 
Persist.new(t) 

t.foo # invokes callback 

Ecco il codice che ho usato per farlo funzionare con le classi:

class Persist 
    class Proxy 
    instance_methods.each { |m| 
     undef_method m unless m =~ /(^__|^send$|^object_id$)/ 
    } 

    def initialize(object) 
     @_persist = object 
    end 

    protected 
     def method_missing(sym, *args) 
     puts "Called #{sym}" 
     @_persist.send(sym, *args) 
     end 
    end 


    attr_reader :object, :proxy 

    def initialize(object) 
    @object = object 
    @proxy = Proxy.new(@object) 
    if object.respond_to?(:name) 
     silence_warnings do 
     Object.const_set(@object.name, @proxy) 
     end 
    end 
    end 
end 
+0

In realtà, il ripensamento del metodo dei ripensamenti potrebbe essere la strada da percorrere - funzionerebbe anche con le istanze: https://gist.github.com/5226decf57adc11baf46 –

1

stai cercando di agganciare un metodo di una classe esempio? Quindi il seguente frammento potrebbe aiutare.Esso utilizza RCapture che può essere installato tramite

gem install rcapture 

Un articolo introduttive sono disponibili all'indirizzo here

require 'rcapture' 

class Test 
    include RCapture::Interceptable 
end 

Test.capture_post :class_methods => :items do 
    puts "items!" 
end 

Test.items 
#=> items! 
+0

dal famoso sviluppatore di RCapture stesso! – martinus

0

Il mio approccio a questo sarebbe di avvolgere l'oggetto che sto cercando di accedere con un guscio Logger oggetto che richiama semplicemente l'oggetto originale. Il codice seguente funziona avvolgendo l'oggetto che si desidera registrare con una classe che chiama semplicemente qualsiasi metodo desiderato sull'oggetto sottostante, ma fornisce un modo per intercettare quelle chiamate e registrare (o qualsiasi altra cosa) ogni evento di accesso.

class Test 
    def self.items 
    puts " Class Items run" 
    "Return" 
    end 

    def item 
    puts " Instance item run" 
    return 47, 11 
    end 
end 

class GenericLogger 
    @@klass = Object # put the class you want to log into @@klass in a sub-class 
    def initialize(*args) 
    @instance = @@klass.new(*args) 
    end 
    def self.method_missing(meth, *args, &block) 
    retval = handle_missing(@@klass, meth, *args, &block) 
    if !retval[0] 
     super 
    end 
    retval[1] 
    end 

    def method_missing(meth, *args, &block) 
    retval = self.class.handle_missing(@instance, meth, *args, &block) 
    if !retval[0] 
     super 
    end 
    retval[1] 
    end 

    def self.handle_missing(obj, meth, *args, &block) 
    retval = nil 
    if obj.respond_to?(meth.to_s) 
     # PUT YOUR LOGGING CODE HERE 
     if obj.class.name == "Class" 
     puts "Logger code run for #{obj.name}.#{meth.to_s}" 
     else 
     puts "Logger code run for instance of #{obj.class.name}.#{meth.to_s}" 
     end 
     retval = obj.send(meth, *args) 
     return true, retval 
    else 
     return false, retval 
    end 
    end 
end 

# When you want to log a class, create one of these sub-classes 
# and place the correct class you are logging in @@klass 
class TestLogger < GenericLogger 
    @@klass = Test 
end 

retval = TestLogger.items 
puts "Correctly handles return values: #{retval}" 
tl = TestLogger.new 
retval = tl.item 
puts "Correctly handles return values: #{retval}" 

begin 
    tl.itemfoo 
rescue NoMethodError => e 
    puts "Correctly fails with unknown methods for instance of Test:" 
    puts e.message 
end 

begin 
    TestLogger.itemsfoo 
rescue NoMethodError => e 
    puts "Correctly fails with unknown methods for class Test" 
    puts e.message 
end 

uscita da quel codice di esempio è:

Logger code run for Test.items 
    Class Items run 
Correctly handles return values: Return 
Logger code run for instance of Test.item 
    Instance item run 
Correctly handles return values: [47, 11] 
Correctly fails with unknown methods for instance of Test: 
undefined method `itemfoo' for #<TestLogger:0x2962038 @instance=#<Test:0x2962008>> 
Correctly fails with unknown methods for class Test 
undefined method `itemsfoo' for TestLogger:Class 
0

singleton_method_added può dare una soluzione semplice:

class Item 
    @added_methods = [] 
    class << self 
    def singleton_method_added name 
     if name != :singleton_method_added && [email protected]_methods.include?(name) 
     @added_methods << name 
     pMethod = self.singleton_method name 
     self.singleton_class.send :define_method, name do |*args, &blk| 
     puts "Callback functions calling..." 
     pMethod.call(*args, &blk) 
     end 
    end 
    end 

    def speak 
    puts "This is #{self}" 
    end 
end 

Spero che questo vi aiuterà.