Ruby: Require vs Require_relative - best practice per risolvere il problema in esecuzione su Ruby <1.9.2 e> = 1.9.2


153

Qual è la migliore pratica se voglio requireun file relativo in Ruby e voglio che funzioni sia in 1.8.xe> = 1.9.2?

Vedo alcune opzioni:

  • fai $LOAD_PATH << '.'e dimentica tutto
  • fare $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • controlla se RUBY_VERSION<1.9.2, quindi definisci require_relativecome require, usa require_relativeovunque dove è necessario in seguito
  • controlla se require_relativeesiste già, in caso affermativo, prova a procedere come nel caso precedente
  • usa strane costruzioni come - ahimè non sembrano funzionare a fondo in Ruby 1.9, perché, ad esempio:
    require File.join(File.dirname(__FILE__), 'path/to/file')
    $ cat caller.rb
    require File.join(File.dirname(__FILE__), 'path/to/file')
    $ cat path/to/file.rb
    puts 'Some testing'
    $ ruby caller
    Some testing
    $ pwd
    /tmp
    $ ruby /tmp/caller
    Some testing
    $ ruby tmp/caller
    tmp/caller.rb:1:in 'require': no such file to load -- tmp/path/to/file (LoadError)
        from tmp/caller.rb:1:in '<main>'
  • Costruzione ancora più strana: sembra funzionare, ma è strana e non abbastanza bella.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Usa la gemma dei backport : è un po 'pesante, richiede un'infrastruttura rubygems e include tonnellate di altre soluzioni alternative, mentre voglio solo requirelavorare con i file relativi.

StackOverflow ha una domanda strettamente correlata che fornisce alcuni altri esempi, ma non fornisce una risposta chiara, che è una buona pratica.

Esiste una soluzione universale accettabile da parte di tutti per far funzionare la mia applicazione su Ruby <1.9.2 e> = 1.9.2?

AGGIORNARE

Chiarimento: non voglio solo risposte come "puoi fare X" - in effetti, ho già menzionato la maggior parte delle scelte in questione. Voglio una motivazione , ovvero perché è una buona pratica, quali sono i suoi pro e contro e perché dovrebbe essere scelta tra le altre.


3
Ciao sono nuovo Qualcuno potrebbe spiegare dall'inizio: qual è la differenza tra requiree require_relative?
Colonnello Panic,

3
Nella versione precedente di Ruby 1.8 se si eseguiva il file a.rbe si voleva che l'interprete leggesse e analizzasse il contenuto del file b.rbnella directory corrente (di solito la stessa directory con a.rb), si scriverebbe require 'b'e andrebbe bene come percorso di ricerca predefinito incluso directory corrente. In Ruby 1.9 più moderno, dovrai scrivere require_relative 'b'in questo caso, come cercherebbe require 'b'solo nei percorsi di libreria standard. Questa è la cosa che interrompe la compatibilità avanti e indietro per gli script più semplici che non verranno installati correttamente (ad esempio, gli script di installazione stessi).
GreyCat,

Ora puoi usare backportssolo per require_relative, vedi la mia risposta ...
Marc-André Lafortune

Risposte:


64

Una soluzione alternativa per questo è stata appena aggiunta alla gemma "aws", quindi ho pensato di condividere come è stato ispirato da questo post.

https://github.com/appoxy/aws/blob/master/lib/awsbase/require_relative.rb

unless Kernel.respond_to?(:require_relative)
  module Kernel
    def require_relative(path)
      require File.join(File.dirname(caller[0]), path.to_str)
    end
  end
end

Questo ti permette di usare require_relativecome faresti in ruby ​​1.9.2 in ruby ​​1.8 e 1.9.1.


3
Come si richiede il file request_relative.rb? Devi richiedere require_relative.rb e quindi request_relative il resto dei requisiti. Oppure mi sfugge qualcosa?
ethicalhack3r

7
La require_relativefunzione è inclusa in un progetto di estensione delle librerie principali di Ruby, disponibile qui: rubyforge.org/projects/extensions Dovresti essere in grado di installarle con gem install extensions. Quindi nel tuo codice aggiungi la seguente riga prima di require_relative: richiedono 'extensions / all' (proveniente dal post di Aurril qui )
thegreendroid

@ ethicalhack3r basta copiare e incollare quel codice nella parte superiore del tuo script ruby ​​o, se nelle rotaie, gettarlo nella parte superiore environment.rb o qualcosa del genere.
Travis Reeder,

46

Prima di fare il salto in 1.9.2 ho usato quanto segue per i relativi requisiti:

require File.expand_path('../relative/path', __FILE__)

È un po 'strano la prima volta che lo vedi, perché sembra che ci sia un ".." in più all'inizio. Il motivo è che expand_pathespanderà un percorso relativo al secondo argomento e il secondo argomento verrà interpretato come se fosse una directory. __FILE__ovviamente non è una directory, ma non importa dal momento expand_pathche non importa se i file esistono o no, applicherà solo alcune regole per espandere cose come .., .e ~. Se riesci a superare l'iniziale "waitaminute non c'è un extra ..lì?" Penso che la riga sopra funzioni abbastanza bene.

Supponendo che __FILE__sia /absolute/path/to/file.rb, ciò che accade è che expand_pathcostruirà la stringa /absolute/path/to/file.rb/../relative/pathe quindi applicherà una regola che dice che ..dovrebbe rimuovere il componente del percorso prima di esso ( file.rbin questo caso), restituendo/absolute/path/to/relative/path .

Questa è la migliore pratica? Dipende da cosa intendi con questo, ma sembra che sia su tutta la base di codice di Rails, quindi direi che è almeno un idioma abbastanza comune.


1
Lo vedo anche comunemente. È brutto, ma sembra funzionare bene.
yfeldblum,

12
un po 'più pulito: richiedono File.expand_path (' relative / path ', File.dirname ( FILE ))
Yannick Wurm,

1
Non penso sia molto più pulito, è solo più lungo. Sono entrambi fugacemente infernali, e quando scelgo tra due cattive opzioni preferisco quella che richiede meno battitura.
Theo,

6
Sembra che File.expand_path ('../ relpath.x', File.dirname ( FILE )) sia un linguaggio migliore, anche se è più prolisso. Affidarsi alla funzionalità discutibilmente interrotta di un percorso di file che viene interpretato come un percorso di directory con una directory inesistente aggiuntiva potrebbe interrompersi quando / se tale funzionalità è fissa.
jpgeek,

1
Rotto, forse, ma è stato così per sempre in UNIX. Non c'è alcuna differenza tra una directory e un file quando si tratta di percorsi e la risoluzione di '..' - quindi non sto perdendo alcun sonno su di esso.
Theo,

6

Il piccone ha un frammento per questo per 1.8. Ecco qui:

def require_relative(relative_feature)
  c = caller.first
  fail "Can't parse #{c}" unless c.rindex(/:\d+(:in `.*')?$/)
  file = $`
  if /\A\((.*)\)/ =~ file # eval, etc.
    raise LoadError, "require_relative is called in #{$1}"
  end
  absolute = File.expand_path(relative_feature, File.dirname(file))
  require absolute
end

In pratica utilizza solo ciò che Theo ha risposto, ma in questo modo puoi ancora utilizzarlo require_relative.


Come verificare se questo frammento deve essere attivato o non correttamente? Utilizzando $RUBY_VERSIONo controllando se require_relativeesiste direttamente?
GreyCat,

1
Tipo sempre anatra, controlla se require_relativeè definito.
Theo

@Theo @GreyCat sì, vorrei verificare se è necessario. Stavo solo mettendo lo snippet qui per far vedere la gente. Personalmente, uso la risposta di Greg comunque, stavo davvero pubblicando questo perché qualcuno l'aveva menzionato senza averlo da solo.
Paul Hoffer,

6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Non è una buona abitudine di sicurezza: perché dovresti esporre tutta la tua directory?

require './path/to/file'

Questo non funziona se RUBY_VERSION <1.9.2

usare strane costruzioni come

require File.join(File.dirname(__FILE__), 'path/to/file')

Ancora più strana costruzione:

require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')

Usa la gemma dei backport: è un po 'pesante, richiede un'infrastruttura rubygems e include tonnellate di altre soluzioni alternative, mentre voglio solo lavorare con i relativi file.

Hai già risposto perché queste non sono le migliori opzioni.

controlla se RUBY_VERSION <1.9.2, quindi definisci request_relative come richiesto, usa Require_relative ovunque sia necessario successivamente

controlla se request_relative esiste già, in caso affermativo, prova a procedere come nel caso precedente

Potrebbe funzionare, ma esiste un modo più sicuro e rapido: gestire l'eccezione LoadError:

begin
  # require statements for 1.9.2 and above, such as:
  require "./path/to/file"
  # or
  require_local "path/to/file"
rescue LoadError
  # require statements other versions:
  require "path/to/file"
end

5

Sono un fan dell'utilizzo della gemma rbx-request-relative ( fonte ). È stato originariamente scritto per Rubinius, ma supporta anche la risonanza magnetica 1.8.7 e non fa nulla in 1.9.2. Richiedere una gemma è semplice e non devo inserire frammenti di codice nel mio progetto.

Aggiungilo al tuo Gemfile:

gem "rbx-require-relative"

Quindi require 'require_relative'prima di terequire_relative .

Ad esempio, uno dei miei file di test è simile al seguente:

require 'rubygems'
require 'bundler/setup'
require 'minitest/autorun'
require 'require_relative'
require_relative '../lib/foo'

Questa è la soluzione più pulita tra queste IMO e la gemma non è pesante come i backport.


4

La backportsgemma ora consente il caricamento individuale dei backport.

Potresti quindi semplicemente:

require 'backports/1.9.1/kernel/require_relative'
# => Now require_relative works for all versions of Ruby

Ciò requirenon influirà sulle versioni più recenti, né aggiornerà altri metodi integrati.


3

Un'altra opzione è quella di dire all'interprete quali percorsi cercare

ruby -I /path/to/my/project caller.rb

3

Un problema che non ho visto sottolineato con le soluzioni basate su __FILE__ è che si rompono per quanto riguarda i collegamenti simbolici. Ad esempio dire che ho:

~/Projects/MyProject/foo.rb
~/Projects/MyProject/lib/someinclude.rb

Lo script principale, il punto di ingresso, l'applicazione è foo.rb. Questo file è collegato a ~ / Scripts / foo che si trova nel mio $ PATH. Questa affermazione obbligatoria viene interrotta quando eseguo 'pippo':

require File.join(File.dirname(__FILE__), "lib/someinclude")

Perché __FILE__ è ~ / Scripts / foo, quindi l'istruzione request sopra cerca ~ / Scripts / foo / lib / someinclude.rb che ovviamente non esiste. La soluzione è semplice Se __FILE__ è un collegamento simbolico, deve essere sottoposto a dereferenziazione. Pathname # realpath ci aiuterà in questa situazione:

richiede "nome percorso"
richiedono File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")

2

Se stavi costruendo una gemma, non vorrai inquinare il percorso di carico.

Ma, nel caso di un'applicazione standalone, è molto conveniente aggiungere la directory corrente al percorso di caricamento come nei primi 2 esempi.

Il mio voto va alla prima opzione nell'elenco.

Mi piacerebbe vedere una solida letteratura sulle migliori pratiche di Ruby.


1
Ri: "Mi piacerebbe vedere alcune solide pubblicazioni sulle migliori pratiche di Ruby." Puoi scaricare Ruby Best Practices di Gregory Brown . Puoi anche consultare il sito delle migliori pratiche di Rails .
Michael Stalker,

1

Definirei il mio relative_requirese non esistesse (cioè sotto 1.8) e quindi utilizzerei la stessa sintassi ovunque.


0

Ruby on Rails:

config_path = File.expand_path("../config.yml", __FILE__)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.