2008-08-14 9 views
34

Ok, also habe ich meinen Code in meiner kleinen Rails App umstrukturiert, um Duplikate zu entfernen, und im Allgemeinen mein Leben einfacher zu machen (wie ich ein einfaches Leben mag). Teil dieses Refactorings war es, Code, der zwei meiner Modelle gemeinsam hat, in ein Modul zu verschieben, das ich dort einfügen kann, wo ich es brauche.Ruby Mixins und Calling Super Methoden

So weit, so gut. Sieht so aus, als würde es klappen, aber ich habe gerade ein Problem gefunden, von dem ich nicht weiß, wie ich es schaffen soll. Das Modul (das ich als sendbar bezeichnet habe) ist nur der Code, der das Faxen, E-Mailen oder Drucken eines PDFs des Dokuments übernimmt. So habe ich zum Beispiel eine Bestellung und ich habe interne Verkaufsaufträge (imaginativ abgekürzt zu ISO).

Das Problem, das ich getroffen habe, ist, dass ich einige Variablen initialisiert werden soll (für Menschen initialisiert, die buchstabieren nicht korrekt: P), nachdem das Objekt geladen wird, so habe ich die after_initialize Haken verwendet worden. Kein Problem ... bis ich anfange, weitere Mixins hinzuzufügen.

Das Problem, das ich habe, ist, dass ich ein after_initialize in einem meiner Mixins haben kann, so brauche ich einen Super Anruf am Start sind die anderen mixin after_initialize Anrufe aufgerufen, um sicherzustellen, erhalten . Was toll ist, bis ich am Ende super anrufe und ich einen Fehler bekomme, weil es keinen Super zu callen gibt.

Hier ist ein kleines Beispiel für den Fall, habe ich nicht genug ist verwirrend:

class Iso < ActiveRecord::Base 
    include Shared::TracksSerialNumberExtension 
    include Shared::OrderLines 
    extend Shared::Filtered 
    include Sendable::Model 

    validates_presence_of :customer 
    validates_associated :lines 

    owned_by    :customer 
    order_lines    :despatched # Mixin 

    tracks_serial_numbers :items # Mixin 

    sendable :customer      # Mixin 

    attr_accessor :address 

    def initialize(params = nil) 
    super 
    self.created_at ||= Time.now.to_date 
    end 
end 

Also, wenn jeder der Mixins einen after_initialize Anruf haben, mit einem Super Anruf, wie kann ich aufhören dass letzte Super Anruf von der Erhöhung des Fehlers? Wie kann ich testen, ob die Super-Methode existiert, bevor ich sie anrufe?

Antwort

40

Sie können diese verwenden:

super if defined?(super) 

Hier ist ein Beispiel:

class A 
end 

class B < A 
    def t 
    super if defined?(super) 
    puts "Hi from B" 
    end 
end 

B.new.t 
0

Anstatt zu überprüfen, ob die Super-Methode existiert, können Sie es nur

class ActiveRecord::Base 
    def after_initialize 
    end 
end 

definieren Dies funktioniert in meinen Tests und sollten keinesfalls Ihre vorhandenen Code brechen, weil alle anderen Klassen, die es definieren wird diese Methode sowieso still außer Kraft setzen

+1

downvoted, weil es keine allgemeine Lösung. Der Punkt ist, dass Sie nicht wissen, ob das Super existiert oder nicht, so dass ActiveRecord :: Base # nach dem Initialisieren monkeypatching, erstellen Sie einen Punkt, wo Ihr Code bricht, wenn ActiveRecord eine Base # nach_initialisieren, oder seine Arity geändert wird ; es ist viel besser, es bedingungslos zu nennen, wenn es definiert ist. – yaauie

+0

@yaauie - sicher, ich hätte eine 'raise 'oh nein' wenn Methoden.include? (: After_initialize)' vor dem Monkeypatch, aber das würde das Beispiel schwerer zu verstehen machen ... Es ist so einfach, eingeholt zu werden in der Detaillierung aller Randfälle, die die eigentliche Lektion hier (patch einfach eine Basismethode in) würde im Rauschen verloren gehen. –

+1

Monkeypatching ist keine allgemeine Lösung und sollte im Allgemeinen nicht gefördert werden. Eine Antwort, die einen einmaligen Monekypatch enthält, der möglicherweise funktionieren kann, führt zu unnötig komplexem und fragilem Code, insbesondere wenn Sie die gesamte Vererbungskette nicht kontrollieren können. – yaauie

3

Haben Sie versucht alias_method_chain? Sie können grundsätzlich alle Ihre after_initialize Anrufe angekettet. Es verhält sich wie ein Dekorator: Jede neue Methode fügt eine neue Funktionsebene hinzu und übergibt das Steuerelement an die "überschriebene" Methode, um den Rest zu erledigen.

3

Die einschließlich Klasse (das Ding, das von ActiveRecord::Base erbt, welche in diesem Fall Iso ist) after_initialize seine eigene definieren könnte, so dass jede andere Lösung als alias_method_chain (oder andere Aliasing, die die ursprüngliche speichert) die Risiken Code zu überschreiben. @ Orion Edwards 'Lösung ist die beste, die ich mir vorstellen kann. Es gibt andere, aber sie sind weit mehr hackish.

alias_method_chain hat auch den Vorteil, benannte Versionen der after_initialize-Methode zu erstellen, was bedeutet, dass Sie die Aufrufreihenfolge in den seltenen Fällen anpassen können, auf die es ankommt. Andernfalls sind Sie der Reihenfolge ausgeliefert, in der die einschließende Klasse die Mixins einschließt.

später:

ich eine Frage an die Rubin-on-Rails-Core-Mailing-Liste geschrieben haben über Standard leere Implementierungen aller Rückrufe zu schaffen. Der Speichervorgang überprüft sie trotzdem alle, deshalb sehe ich nicht, warum sie nicht dort sein sollten. Der einzige Nachteil ist die Erstellung von zusätzlichen leeren Stack-Frames, aber das ist bei jeder bekannten Implementierung ziemlich billig.

2

Sie können nur eine schnelle bedingte werfen dort:

super if respond_to?('super') 

und Sie sollten in Ordnung sein - keine Zugabe von nutzlosen Methoden; schön und sauber.

+0

Das scheint ** nicht ** zu funktionieren. respond_to ('super') gibt immer false zurück. – averell