2012-01-10 16 views
5

Questo si spiega meglio con un esempio:I metodi importati da Ruby sono sempre privati?

file1.rb:

def foo 
    puts 123 
end 

file2.rb:

class A 
    require 'file1' 
end 
A.new.foo 

darà un errore "': metodo privato 'foo' chiamato" .

Posso aggirare questo facendo A.new.send("foo") ma c'è un modo per rendere pubblici i metodi importati?

Modifica: per chiarire, non sto confondendo includere e richiedere. Inoltre, il motivo per cui non posso usare l'inclusione normale (come molti hanno giustamente sottolineato) è che questo fa parte di una configurazione di meta-programmazione. Devo consentire all'utente di aggiungere funzionalità in fase di esecuzione; ad esempio, può dire "run-this-app --include file1.rb" e l'app si comporterà in modo diverso in base al codice che ha scritto in file1.rb. Spiacente dovrebbe aver spiegato più chiaro.

Modifica: dopo aver letto la risposta di Jorg, mi sono reso conto che il mio codice non si comporta esattamente come previsto, e risponde alla mia domanda (erroneamente) perfettamente. Sto cercando di fare qualcosa di più simile a str=(entire file1.rb as string); A.class_exec(str).

+0

Hai provato 'A.new.instance_eval {foo}'? Non funziona per me (rubino 1.9.2). – knut

+0

Stai confondendo 'require con' include'? –

+0

@knut l'ho fatto. Funziona. – alexloh

risposta

7

procedure globali in Ruby non sono davvero procedure globali. Sono metodi, come tutto il resto. In particolare, quando si definisce cosa è come una procedura globale, si è in realtà che definisce un metodo di istanza privato di Object. Poiché ogni parte del codice in Ruby viene valutata nel contesto di un oggetto, questo consente di utilizzare tali metodi come se fossero procedure globali, poiché self è il destinatario predefinito e self è un oggetto la cui classe eredita da Object.

Quindi, questo:

# file1.rb 

def foo 
    puts 123 
end 

è in realtà equivale a

# file1.rb 

class Object 
    private 

    def foo 
    puts 123 
    end 
end 

Ora avete una "procedura globale" chiamato foo, che si può chiamare proprio come questo:

foo 

motivo perché è possibile chiamarlo così, è che questa chiamata è in realtà equivalente a

self.foo 

e self è un oggetto che include Object nella sua catena discendenza, quindi eredita il metodo privato foo.

[Nota: per essere precisi, i metodi privati ​​non possono essere chiamati con un ricevitore esplicito, anche se il destinatario esplicito è self. Così, per essere davvero pedante, è realtà equivalente a self.send(:foo) e non self.foo]

Il A.new.foo nel vostro file2.rb è una falsa pista:. Si potrebbe altrettanto bene provare Object.new.foo o [].foo o 42.foo e ottenere lo stesso risultato .

proposito: puts e require sono essi stessi esempi di tali "procedure globali", che sono in realtà metodi privati ​​Object (o più precisamente, si tratta di metodi privati ​​Kernel che viene miscelata in Object).

Su un sidenote: è davvero cattivo stile di mettere le chiamate a require all'interno di una definizione di classe, perché lo fa apparire come il codice require D è in qualche modo con ambito o namespace all'interno della classe, che è ovviamente falso. require semplicemente esegue il codice nel file, niente di più.

Così, mentre

# file2.rb 

class A 
    require 'file1.rb' 
end 

è il codice perfettamente valido, ma è anche molto confusa. È molto meglio usare il seguente, semanticamente equivalente, codice:

# file2.rb 

require 'file1.rb' 

class A 
end 

questo modo è perfettamente chiaro al lettore del codice che file1.rb è in alcun modo ambito o namespace all'interno A.

Inoltre, in genere è preferibile lasciare l'estensione del file, ovvero utilizzare require 'file1' anziché require 'file1.rb'. Ciò consente di sostituire il file Ruby con, ad esempio, codice nativo (per MRI, YARV, Rubinius, MacRuby o JRuby), codice byte JVM in un file .jar o .class (per JRuby), codice byte CIL nel file .dll (per IronRuby) e così via, senza dover cambiare nessuna delle tue chiamate require.

Un ultimo commento: il modo idiomatico per aggirare la protezione di accesso è quella di utilizzare send, non instance_eval, vale a dire utilizzare A.new.send(:foo) invece di A.new.instance_eval {foo}.

+0

Grazie! Questa risposta è davvero completa e chiarisce le incomprensioni che avevo - che richiedevano era come C includere quel testo incollato alla copia. C'è un modo per aggiungere i contenuti di file1.rb come metodi di classe A in modo dinamico?Sia il contenuto che la posizione di file1 sono noti solo in fase di esecuzione e l'utente non è a conoscenza di A. Oppure è possibile trovare l'elenco di metodi o moduli definiti in un file? – alexloh

10

Questo è un brutto modo per farlo in Ruby. Provare a utilizzare mixins tramite moduli invece:

file1.rb:

module IncludesFoo 
    def foo 
    puts 123 
    end 
end 

file2.rb:

require 'file1.rb' 

class A 
    include IncludesFoo 
end 

A.new.foo 
# => 123 
+0

Sia 'file1.rb' che il suo contenuto provengono da input dell'utente e vengono caricati dinamicamente. In pratica l'utente può inserire un percorso in fase di esecuzione, e l'applicazione caricherà il file rubino appropriata che altererà funzionalità esistenti, senza riavviare, ecc dispiace avrebbe dovuto più chiaro. – alexloh

Problemi correlati