Ruby: require vs require_relative - meilleure pratique pour contourner l'exécution dans Ruby <1.9.2 et> = 1.9.2

153

Quelle est la meilleure pratique si je veux requireun fichier relatif dans Ruby et que je veux qu'il fonctionne à la fois dans 1.8.x et> = 1.9.2?

Je vois quelques options:

  • fais $LOAD_PATH << '.'et oublie tout
  • faire $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • vérifier si RUBY_VERSION<1.9.2, puis définir require_relativecomme require, utiliser require_relativepartout où c'est nécessaire par la suite
  • vérifiez s'il require_relativeexiste déjà, si c'est le cas, essayez de procéder comme dans le cas précédent
  • utilisez des constructions étranges telles que - hélas, elles ne semblent pas fonctionner complètement dans Ruby 1.9, car, par exemple:
    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>'
  • Construction encore plus étrange: semble fonctionner, mais c'est étrange et pas très beau.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Utilisez la gemme de backports - c'est un peu lourd, cela nécessite une infrastructure rubygems et inclut des tonnes d'autres solutions de contournement, alors que je veux juste requiretravailler avec des fichiers relatifs.

Il y a une question étroitement liée chez StackOverflow qui donne quelques exemples supplémentaires, mais elle ne donne pas de réponse claire - ce qui est une meilleure pratique.

Existe-t-il une solution universelle décente et acceptée par tous pour faire fonctionner mon application à la fois sur Ruby <1.9.2 et> = 1.9.2?

METTRE À JOUR

Clarification: je ne veux pas seulement des réponses comme "vous pouvez faire X" - en fait, j'ai déjà mentionné la plupart des choix en question. Je veux une justification , c'est-à-dire pourquoi c'est une meilleure pratique, quels sont ses avantages et ses inconvénients et pourquoi elle devrait être choisie parmi les autres.

GreyCat
la source
3
Salut, je suis nouveau. Quelqu'un pourrait-il expliquer dès le départ - quelle est la différence entre requireet require_relative?
Colonel Panic
3
Dans l'ancien Ruby 1.8, si vous exécutiez un fichier a.rbet que vous vouliez que l'interpréteur lise et analyse le contenu du fichier b.rbdans le répertoire actuel (généralement le même répertoire qu'avec a.rb), vous écrivez simplement require 'b'et ce serait bien car le chemin de recherche par défaut incluait le répertoire actuel. Dans Ruby 1.9 plus moderne, vous devrez écrire require_relative 'b'dans ce cas, comme le require 'b'ferait une recherche uniquement dans les chemins de bibliothèque standard. C'est ce qui rompt la compatibilité ascendante et descendante pour les scripts plus simples qui ne seront pas installés correctement (par exemple, installer les scripts eux-mêmes).
GreyCat
Vous pouvez maintenant utiliser backportsjuste pour require_relative, voir ma réponse ...
Marc-André Lafortune

Réponses:

64

Une solution de contournement pour cela a été juste ajoutée à la gemme 'aws', alors j'ai pensé que je la partagerais car elle était inspirée par cet article.

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

Cela vous permet d'utiliser require_relativecomme vous le feriez dans ruby ​​1.9.2 dans ruby ​​1.8 et 1.9.1.

Travis Reeder
la source
3
Comment avez-vous besoin du fichier require_relative.rb? Vous devez exiger require_relative.rb, puis require_relative le reste des require. Ou est-ce que je manque quelque chose?
ethiquehack3r
7
La require_relativefonction est incluse dans un projet d'extension des bibliothèques de base Ruby, trouvé ici: rubyforge.org/projects/extensions Vous devriez pouvoir les installer avec gem install extensions. Ensuite, dans votre code, ajoutez la ligne suivante avant le require_relative: require 'extensions / all' (provenant du message d'Aurril ici )
thegreendroid
@ ethiquehack3r copiez et collez simplement ce code en haut de votre script ruby ​​ou s'il se trouve dans des rails, lancez-le en haut environment.rb ou quelque chose comme ça.
Travis Reeder
46

Avant de passer à la 1.9.2, j'ai utilisé ce qui suit pour les exigences relatives:

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

C'est un peu bizarre la première fois que vous le voyez, car on dirait qu'il y a un "..." supplémentaire au début. La raison en est que expand_pathcela développera un chemin par rapport au second argument, et le second argument sera interprété comme s'il s'agissait d'un répertoire. __FILE__Ce n'est évidemment pas un répertoire, mais cela n'a pas d'importance car peu importe expand_pathsi les fichiers existent ou non, il appliquera simplement quelques règles pour développer des choses comme .., .et ~. Si vous pouvez surmonter le "waitaminute initial, n'y a-t-il pas un supplément ..?" Je pense que la ligne ci-dessus fonctionne assez bien.

En supposant que __FILE__c'est le cas /absolute/path/to/file.rb, ce qui se passe est que expand_pathcela construira la chaîne /absolute/path/to/file.rb/../relative/path, puis appliquera une règle qui dit que ..devrait supprimer le composant de chemin avant lui ( file.rbdans ce cas), en retournant /absolute/path/to/relative/path.

Est-ce la meilleure pratique? Cela dépend de ce que vous entendez par là, mais il semble que tout soit dans la base de code de Rails, alors je dirais que c'est au moins un idiome assez courant.

Théo
la source
1
Je vois cela aussi souvent. C'est moche, mais cela semble bien fonctionner.
yfeldblum
12
un peu plus propre: nécessite File.expand_path ('relative / path', File.dirname ( FILE ))
Yannick Wurm
1
Je ne pense pas que ce soit beaucoup plus propre, c'est juste plus long. Ils sont tous les deux fugaces, et au moment de choisir entre deux mauvaises options, je préfère celle qui nécessite moins de frappe.
Theo
6
Il semble que File.expand_path ('../ relpath.x', File.dirname ( FILE )) soit un meilleur idiome, même s'il est plus détaillé. S'appuyer sur la fonctionnalité sans doute cassée d'un chemin de fichier interprété comme un chemin de répertoire avec un répertoire supplémentaire inexistant peut être interrompu lorsque / si cette fonctionnalité est corrigée.
jpgeek
1
Cassé, peut-être, mais cela a toujours été le cas sous UNIX. Il n'y a simplement aucune différence entre un répertoire et un fichier en ce qui concerne les chemins et la résolution de «..» - donc je ne perds pas le temps de dormir dessus.
Theo
6

La pioche a un extrait pour cela pour 1.8. C'est ici:

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

Il utilise essentiellement ce que Theo a répondu, mais vous pouvez toujours l'utiliser require_relative.

Paul Hoffer
la source
Comment vérifier si cet extrait doit être activé ou pas correctement? En utilisant $RUBY_VERSIONou en vérifiant s'il require_relativeexiste directement?
GreyCat
1
Toujours le type de canard, vérifiez si require_relativeest défini.
Theo
@Theo @GreyCat oui, je vérifierais si c'est nécessaire. Je mettais juste l'extrait ici pour que les gens le montrent. Personnellement, j'utiliserais la réponse de Greg de toute façon, je publiais simplement ceci parce que quelqu'un l'avait mentionné sans l'avoir lui-même.
Paul Hoffer
6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Ce n'est pas une bonne habitude de sécurité: pourquoi devriez-vous exposer tout votre répertoire?

require './path/to/file'

Cela ne fonctionne pas si RUBY_VERSION <1.9.2

utiliser des constructions étranges telles que

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

Construction encore plus étrange:

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

Utilisez la gemme de backports - c'est un peu lourd, cela nécessite une infrastructure rubygems et inclut des tonnes d'autres solutions de contournement, alors que je veux juste avoir besoin de travailler avec des fichiers relatifs.

Vous avez déjà expliqué pourquoi ce ne sont pas les meilleures options.

vérifiez si RUBY_VERSION <1.9.2, puis définissez require_relative comme require, utilisez require_relative partout où c'est nécessaire par la suite

vérifiez si require_relative existe déjà, si c'est le cas, essayez de procéder comme dans le cas précédent

Cela peut fonctionner, mais il existe un moyen plus sûr et plus rapide: pour gérer l'exception 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
Claudio Floreani
la source
5

Je suis fan de l'utilisation de la gemme relative rbx-require-relative ( source ). Il a été écrit à l'origine pour Rubinius, mais il prend également en charge MRI 1.8.7 et ne fait rien dans 1.9.2. Exiger une gemme est simple et je n'ai pas à lancer d'extraits de code dans mon projet.

Ajoutez-le à votre Gemfile:

gem "rbx-require-relative"

Puis require 'require_relative'devant toi require_relative.

Par exemple, l'un de mes fichiers de test ressemble à ceci:

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

C'est la solution la plus propre de tous ces IMO, et le bijou n'est pas aussi lourd que les backports.

Edward Anderson
la source
4

La backportsgemme permet désormais le chargement individuel des backports.

Vous pourriez alors simplement:

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

Cela requiren'affectera pas les versions plus récentes et ne mettra pas à jour les autres méthodes intégrées.

Marc-André Lafortune
la source
3

Une autre option consiste à indiquer à l'interpréteur les chemins à rechercher

ruby -I /path/to/my/project caller.rb
eradman
la source
3

Un problème que je n'ai pas vu souligné avec les solutions basées sur __FILE__ est qu'elles se cassent en ce qui concerne les liens symboliques. Par exemple, disons que j'ai:

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

Le script principal, le point d'entrée, l'application est foo.rb. Ce fichier est lié à ~ / Scripts / foo qui se trouve dans mon $ PATH. Cette instruction require est interrompue lorsque j'exécute 'foo':

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

Parce que __FILE__ est ~ / Scripts / foo, l'instruction require ci-dessus recherche ~ / Scripts / foo / lib / someinclude.rb qui n'existe évidemment pas. La solution est simple. Si __FILE__ est un lien symbolique, il doit être déréférencé. Pathname # realpath nous aidera dans cette situation:

exiger "chemin"
require File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")
jptros
la source
2

Si vous construisez une gemme, vous ne voudriez pas polluer le chemin de chargement.

Mais, dans le cas d'une application autonome, il est très pratique d'ajouter simplement le répertoire actuel au chemin de chargement comme vous le faites dans les 2 premiers exemples.

Mon vote va à la première option de la liste.

J'aimerais voir une documentation solide sur les meilleures pratiques Ruby.

Casey Watson
la source
1
Re: "J'adorerais voir de la documentation solide sur les meilleures pratiques Ruby." Vous pouvez télécharger les meilleures pratiques Ruby de Gregory Brown . Vous pouvez également consulter le site des meilleures pratiques Rails .
Michael Stalker
1

Je définirais le mien relative_requires'il n'existe pas (c'est-à-dire sous 1.8) et utiliserais la même syntaxe partout.

Phrogz
la source
0

Ruby on Rails façon:

config_path = File.expand_path("../config.yml", __FILE__)
Vaibhav
la source