2010-11-18 5 views
3

Ich möchte benachrichtigt werden, wenn bestimmte Dinge in einigen meiner Klassen passieren. Ich möchte dies so einrichten, dass sich die Implementierung meiner Methoden in diesen Klassen nicht ändert.Wie umgehe ich den Aufruf einer Ruby-Methode, indem ich ein Modul einschließe?

Ich dachte ich so etwas wie das folgende Modul haben würde:

class Foo 
    include Notifications 

    # notify that we're running :bar, then run bar 
    notify_when :bar 

    def bar(...) # bar may have any arbitrary signature 
    # ... 
    end 
end 

Mein Schlüssel Wunsch ist, dass ich nicht‘:

module Notifications 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def notify_when(method) 
     puts "the #{method} method was called!" 
     # additional suitable notification code 
     # now, run the method indicated by the `method` argument 
    end 
    end 
end 

Dann habe ich es in meinen Klassen wie so mischen Ich möchte :bar ändern müssen, damit Benachrichtigungen ordnungsgemäß funktionieren. Kann das gemacht werden? Wenn ja, wie würde ich die notify_when Implementierung schreiben?

Auch ich benutze Rails 3, also wenn es ActiveSupport oder andere Techniken gibt, die ich verwenden kann, bitte zögern Sie nicht zu teilen. (Ich sah ActiveSupport::Notifications, aber das würde erfordern mir die bar Methode zu ändern.)


es zu meiner Aufmerksamkeit gekommen ist, dass ich will vielleicht „die Module + Super-Trick“ verwenden. Ich bin mir nicht sicher, was das ist - vielleicht kann mich jemand aufklären?

Antwort

7

Ich stelle mir vor, Sie könnten eine Alias-Methodenkette verwenden.

Etwas wie folgt aus:

def notify_when(method) 
    alias_method "#{method}_without_notification", method 
    define_method method do |*args| 
    puts "#{method} called" 
    send "#{method}_without_notification", args 
    end 
end 

Sie müssen sich nicht ändern Methoden mit diesem Ansatz.

+0

Was passiert, wenn: Methode nimmt einen Block? –

+0

Sie müssen 'define_method' nicht verwenden, Sie können ein' eval' verwenden, um die Methode zu definieren. Z.B. 'eval" def # {methode} (* args, & block) ... ' –

+0

Dies setzt voraus, dass 'notify_when' nach der Methodendefinition kommt, also wird es in Ihrem Codebeispiel nicht funktionieren. Auch * modifiziert * die' Leiste "Methode. Aber abgesehen von der Verwendung eines Proxy-Objekts haben * Sie * Ihre Methoden zu ändern (schmücken). – horseyguy

3

Ich denke an zwei Ansätze:

(1) Dekorieren Sie die Foo Methoden eine Mitteilung aufzunehmen.

(2) ein Proxy-Objekt zu verwenden, das Verfahren zu Foo dazwischen ruft und benachrichtigt Sie, wenn sie

Die erste Lösung ist der Ansatz von Jakub genommen geschehen, obwohl die alias_method Lösung nicht der beste Weg, um dies zu erreichen ist, verwenden Sie stattdessen:

class Interceptor 
    def initialize(target) 
    @target = target 
    end 

    def method_missing(name, *args, &block) 
    if @target.respond_to?(name) 
     puts "about to run #{name}" 
     @target.send(name, *args, &block) 
    else 
     super 
    end 
    end 
end 

class Hello; def hello; puts "hello!"; end; end 

i = Interceptor.new(Hello.new) 
i.hello #=> "about to run hello" 
     #=> "hello!" 
:

def notify_when(meth) 
    orig_meth = instance_method(meth) 
    define_method(meth) do |*args, &block| 
    puts "#{meth} called" 
    orig_meth.bind(self).call *args, &block 
    end 
end 

Die zweite Lösung, die Sie method_missing in Verbindung mit einem Proxy verwenden erfordert

Die erste Methode erfordert die Modifizierung der Methoden (etwas, das Sie sagten, dass Sie nicht wollten) und die zweite Methode erfordert die Verwendung eines Proxy, vielleicht etwas, das Sie nicht wollen. Es gibt keine einfache Lösung, es tut mir leid.

+0

Könnten Sie bitte eine Erklärung hinzufügen, warum das erneute Binden der ursprünglichen Methode ist besser als mit alias_method? Der Unterschied ist für mich nicht offensichtlich, danke .. – kostja

+0

Nevermind, ich habe [diese nette Erklärung] gefunden (http://stackoverflow.com/questions/4470108). – kostja

11

Es ist schon eine Weile her, seit diese Frage aktiv war, aber es gibt noch eine andere Möglichkeit, Methoden durch ein enthaltenes (oder erweitertes) Modul einzubinden.

Seit 2.0 können Sie ein Modul prepend, effektiv es ist ein Proxy für die Prepending Klasse.

Im folgenden Beispiel wird eine Methode eines erweiterten Modulmoduls aufgerufen, in der die Namen der Methoden übergeben werden, die umschlossen werden sollen. Für jeden der Methodennamen wird ein neues Modul erstellt und vorangestellt. Dies dient der Einfachheit des Codes.Sie können auch mehrere Methoden an einen einzelnen Proxy anhängen.

Ein wichtiger Unterschied zu den Lösungen mit alias_method und instance_method, die später an self gebunden ist, ist, dass Sie die Methoden definieren können, die umschlossen werden sollen, bevor die Methoden selbst definiert werden.

module Prepender 

    def wrap_me(*method_names) 
    method_names.each do |m| 
     proxy = Module.new do 
     define_method(m) do |*args| 
      puts "the method '#{m}' is about to be called" 
      super *args 
     end 
     end 
     self.prepend proxy 
    end 
    end 
end 

Verwendung:

class Dogbert 
    extend Prepender 

    wrap_me :bark, :deny 

    def bark 
    puts 'Bah!' 
    end 

    def deny 
    puts 'You have no proof!' 
    end 
end 

Dogbert.new.deny 

# => the method 'deny' is about to be called 
# => You have no proof! 
Verwandte Themen