2009-07-11 5 views
2

Gibt es eine Version von require in Ruby, die entweder die ganze Datei lädt, oder gar nichts?Alles oder nichts in Ruby erforderlich?

Das Problem ist, dass erfordern beginnt von oben einlegen, und wenn es Problemen stehen Sie mit unvollendeten Definitionen am Ende zum Beispiel lädt die folgend wäre immer noch ein class A auch wenn module C nicht definiert ist:

class B 
    include C 
end 

In meinem speziellen Fall habe ich eine große Menge von voneinander abhängigen Dateien und einen Loader, der diese Dateien lädt. Zur Veranschaulichung werde ich einfach den Satz von Dateien auf 4 Dateien aufteilen (a.rb, b.rb, c.rb und w.rb). Hier finden Sie eine Liste dieser Dateien:

# In file a.rb 
class A 
    @foo = [] 
    @foo.push("in A") 

    def self.inherited(subclass) 
    foo = @foo.dup 
    subclass.instance_eval do 
     @foo = foo 
    end 
    end 

    def self.get_foo 
    @foo 
    end 
end 

# In file b.rb 
class B < A 
    include C# if C is not already defined, the following line will not get executed although B will be defined. 
    @foo.push("in B") 
end 

# In file c.rb 
module C 
end 

# In file w.rb 
class W < B 
    @foo.push("in W") 
end 

Der Lader arbeitet mit einer Liste der aktuellen Dateien bekommen, versuchen, sie eins nach dem anderen zu verlangen. Wenn eine Datei fehlschlägt, bleibt sie in der Liste und wird später erneut versucht. Der Code so etwas wie dieses: (entfernt viele Details der Einfachheit halber)

# In file loader.rb 
files = Dir["*.rb"].reject { |f| f =~ /loader/ } 
files.sort! # just for the purpose of the example, to make them load in an order that causes the problem 
files.reject! { |f| require(f) rescue nil } while files.size > 0 

Ich würde es letztlich A geladen werden soll, dann ist das B finden kann nicht allein geladen werden (so überspringen sie), dann laden C , dann finde W kann noch nicht geladen werden (also überspringe), dann gehe zurück zu B dann W.

In diesem Fall wäre der Ausgang von p W.get_foo["in A", "in B", "in W"], was ich will.

Was tatsächlich passiert ist, dass es A lädt, dann lädt teilweise B, dann C, dann, wenn es zu W kommt, glaubt es, dass es es laden kann (da B bereits definiert ist). Dies löst den self.inherited-Code zu einer falschen Zeit aus und kopiert einen Nicht-Fertig-Wert von @foo, wodurch die Ausgabe p W.get_foo falsch zu ["in A", "in W"] wird.

Mit einem Alles-oder-nichts require würde es lösen.

Irgendwelche Ideen?

Antwort

5

Wenn eine Datei von einer anderen abhängt, sollte diese Datei die Abhängigkeit selbst erfordern. Zum Beispiel sollte b.rb wie folgt aussehen:

require 'a' 
require 'c' 

class B < A 
    include C# if C is not already defined, the following line will not get executed although B will be defined. 
    @foo.push("in B") 
end 

und w.rb sollte wie folgt aussehen:

require 'b' 

class W < B 
    @foo.push("in W") 
end 

Danach wird die äußere Ladereihenfolge keine Rolle mehr spielt, noch hat eine „Alles-oder-nichts " erfordern Ansatz. Wenn b geladen wird, sieht es zuerst die Anforderung für a und realisiert, dass es bereits geladen wurde, dann wird es c erfordern, weil es realisiert, dass es es noch nicht geladen hat. Wenn c erneut benötigt wird, wird es von der äußeren Schleife übersprungen.

Hinweis: Seien Sie vorsichtig mit Ihrem $ LOAD_PATH und den an require übergebenen Pfaden. Ruby erkennt nur doppelte Anforderungen, wenn die Pfade identisch sind. Es ist am besten, relative Pfade (relativ zu einem Pfad in $ LOAD_PATH) anstelle von absoluten Pfaden zu verwenden; Andernfalls wird eine Datei möglicherweise zweimal geladen.

+0

Danke, mir war klar, dass ich 'require' in jeder Datei verwenden konnte, aber ich wollte, dass es _automatic_ ist. Ich kam zu einer komplexen Lösung (Ausführen einer zweiten Ruby-Instanz als Sandbox zu testen erfordert), aber es scheint, das ist die beste Praxis, also werde ich dafür gehen :) –

Verwandte Themen