Ruby: require vs require_relative - Best Practice für die Problemumgehung in Ruby <1.9.2 und> = 1.9.2

153

Was ist die beste Praxis , wenn ich will requireeine relative Datei in Ruby und ich möchte es in beiden 1.8.x arbeiten und> = 1.9.2?

Ich sehe einige Optionen:

  • einfach $LOAD_PATH << '.'alles tun und vergessen
  • machen $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • Überprüfen Sie, ob RUBY_VERSION<1.9.2, und definieren Sie dann require_relativeals require, require_relativeüberall dort verwenden , wo es später benötigt wird
  • Überprüfen Sie, ob dies require_relativebereits vorhanden ist. Versuchen Sie in diesem Fall, wie im vorherigen Fall fortzufahren
  • Verwenden Sie seltsame Konstruktionen wie - leider scheinen sie in Ruby 1.9 nicht vollständig zu funktionieren, weil zum Beispiel:
    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>'
  • Noch seltsamere Konstruktion: scheint zu funktionieren, ist aber seltsam und sieht nicht ganz gut aus.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Verwenden Sie Backports Gem - es ist ziemlich schwer, es erfordert Rubygems-Infrastruktur und enthält Tonnen anderer Problemumgehungen, während ich nur requiremit relativen Dateien arbeiten möchte .

Bei StackOverflow gibt es eine eng verwandte Frage , die einige weitere Beispiele enthält, aber keine klare Antwort gibt - was eine bewährte Methode ist.

Gibt es eine anständige, von allen akzeptierte universelle Lösung, mit der meine Anwendung sowohl auf Ruby <1.9.2 als auch auf> = 1.9.2 ausgeführt werden kann?

AKTUALISIEREN

Klarstellung: Ich möchte nicht nur Antworten wie "Sie können X machen" - tatsächlich habe ich die meisten fraglichen Entscheidungen bereits erwähnt. Ich möchte eine Begründung , dh warum es sich um eine bewährte Methode handelt, welche Vor- und Nachteile sie hat und warum sie unter den anderen ausgewählt werden sollte.

GreyCat
quelle
3
Hallo, ich bin neu. Könnte jemand von Anfang an erklären - was ist der Unterschied zwischen requireund require_relative?
Colonel Panic
3
Wenn Sie in älterem Ruby 1.8 eine Datei ausgeführt haben a.rbund den Interpreter dazu bringen möchten, den Inhalt der Datei b.rbim aktuellen Verzeichnis zu lesen und zu analysieren (normalerweise das gleiche Verzeichnis wie bei a.rb), schreiben Sie einfach require 'b'und dies ist in Ordnung, da der Standardsuchpfad das aktuelle Verzeichnis enthält. In modernerem Ruby 1.9 müssen Sie require_relative 'b'in diesem Fall schreiben , da require 'b'nur in Standardbibliothekspfaden gesucht wird. Das ist die Sache, die die Vorwärts- und Rückwärtskompatibilität für einfachere Skripte unterbricht, die nicht richtig installiert werden (z. B. Skripte selbst installieren ).
GreyCat
Sie können jetzt backportsnur für verwenden require_relative, siehe meine Antwort ...
Marc-André Lafortune

Antworten:

64

Eine Problemumgehung dafür wurde gerade zu dem Juwel "aws" hinzugefügt, also dachte ich, ich würde es teilen, da es von diesem Beitrag inspiriert wurde.

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

Auf diese Weise können Sie require_relativewie in Ruby 1.9.2 in Ruby 1.8 und 1.9.1 verwenden.

Travis Reeder
quelle
3
Wie benötigen Sie die Datei require_relative.rb? Sie müssen require_relative.rb und dann require_relative den Rest der Anforderungen benötigen. Oder fehlt mir etwas?
Ethicalhack3r
7
Die require_relativeFunktion ist in einem Erweiterungsprojekt für die Ruby-Kernbibliotheken enthalten, das Sie hier finden: rubyforge.org/projects/extensions Sie sollten in der Lage sein, sie mit zu installieren gem install extensions. require_relativeFügen Sie dann in Ihrem Code die folgende Zeile vor dem hinzu : erfordern 'extensions / all' (bezogen auf Aurrils Beitrag hier )
thegreendroid
@ ethicalhack3r kopiere und füge diesen Code einfach oben in dein Ruby-Skript ein oder wirf ihn in Schienen in die oberste Umgebung. rb oder so.
Travis Reeder
46

Bevor ich zu 1.9.2 gesprungen bin, habe ich Folgendes für relative Anforderungen verwendet:

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

Es ist ein bisschen komisch, wenn man es zum ersten Mal sieht, weil es so aussieht, als gäbe es am Anfang ein zusätzliches '..'. Der Grund ist, dass expand_pathein Pfad relativ zum zweiten Argument erweitert wird und das zweite Argument so interpretiert wird, als wäre es ein Verzeichnis. __FILE__offensichtlich ist kein Verzeichnis, aber das spielt keine Rolle , da expand_pathist es egal , ob die Dateien vorhanden ist oder nicht, es wird nur einige Regeln anwenden zu erweitern Dinge wie .., .und ~. Wenn Sie über das anfängliche "waitaminute" hinwegkommen können, gibt es dort kein Extra ..? Ich denke, dass die obige Zeile ganz gut funktioniert.

Dass unter der Annahme __FILE__ist /absolute/path/to/file.rb, was passiert ist , dass expand_pathdie Zeichenfolge wird konstruieren /absolute/path/to/file.rb/../relative/pathund dann eine Regel anwenden , die besagen , dass ..sollte die Pfadkomponente , bevor es (entfernen file.rbin diesem Fall), Rückkehr /absolute/path/to/relative/path.

Ist das Best Practice? Kommt darauf an, was du damit meinst, aber es scheint, als ob es überall in der Rails-Codebasis ist, also würde ich sagen, dass es zumindest eine allgemein verbreitete Redewendung ist.

Das Ö
quelle
1
Ich sehe das auch häufig. Es ist hässlich, aber es scheint gut zu funktionieren.
Yfeldblum
12
etwas sauberer: File.expand_path ('relative / path', File.dirname ( FILE )) erforderlich
Yannick Wurm
1
Ich denke nicht, dass es viel sauberer ist, es ist nur länger. Sie sind beide höllisch flüchtig, und wenn ich zwischen zwei schlechten Optionen wähle, bevorzuge ich die, die weniger Eingabe erfordert.
Theo
6
Es scheint, dass File.expand_path ('../relpath.x', File.dirname ( FILE )) eine bessere Redewendung ist, obwohl es ausführlicher ist. Wenn Sie sich auf die möglicherweise fehlerhafte Funktionalität eines Dateipfads verlassen, der als Verzeichnispfad mit einem zusätzlichen nicht vorhandenen Verzeichnis interpretiert wird, kann dies zu einer Unterbrechung führen, wenn diese Funktionalität behoben ist.
Jpgeek
1
Vielleicht kaputt, aber unter UNIX war das schon immer so. Es gibt einfach keinen Unterschied zwischen einem Verzeichnis und einer Datei, wenn es um Pfade und die Auflösung von '..' geht - also verliere ich keinen Schlaf darüber.
Theo
6

Die Spitzhacke hat dafür einen Ausschnitt für 1.8. Hier ist es:

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

Es verwendet im Grunde nur das, was Theo geantwortet hat, aber Sie können es trotzdem verwenden require_relative.

Paul Hoffer
quelle
Wie kann ich überprüfen, ob dieses Snippet aktiviert werden soll oder nicht? Verwenden $RUBY_VERSIONoder überprüfen, ob require_relativedirekt vorhanden?
GreyCat
1
Immer Ententyp, prüfen Sie, ob require_relativedefiniert ist.
Theo
@Theo @GreyCat Ja, ich würde prüfen, ob es benötigt wird. Ich habe nur den Ausschnitt hierher gebracht, damit die Leute ihn zeigen können. Persönlich würde ich Gregs Antwort sowieso verwenden, ich habe das wirklich nur gepostet, weil jemand es erwähnt hatte, ohne es selbst zu haben.
Paul Hoffer
6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

Es ist keine gute Sicherheitsgewohnheit: Warum sollten Sie Ihr gesamtes Verzeichnis verfügbar machen?

require './path/to/file'

Dies funktioniert nicht, wenn RUBY_VERSION <1.9.2

Verwenden Sie seltsame Konstruktionen wie

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

Noch seltsamere Konstruktion:

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

Verwenden Sie Backports Gem - es ist ziemlich schwer, es erfordert eine Rubygems-Infrastruktur und enthält Tonnen anderer Problemumgehungen, während ich nur mit relativen Dateien arbeiten möchte.

Sie haben bereits geantwortet, warum dies nicht die besten Optionen sind.

Überprüfen Sie, ob RUBY_VERSION <1.9.2 ist, definieren Sie require_relative als require und verwenden Sie require_relative überall dort, wo es später benötigt wird

Überprüfen Sie, ob require_relative bereits vorhanden ist. Wenn dies der Fall ist, versuchen Sie, wie im vorherigen Fall fortzufahren

Dies mag funktionieren, aber es gibt einen sichereren und schnelleren Weg: um mit der LoadError-Ausnahme umzugehen:

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
quelle
5

Ich bin ein Fan von der Verwendung des Edelsteins rbx-require-relative ( Quelle ). Es wurde ursprünglich für Rubinius geschrieben, unterstützt aber auch MRI 1.8.7 und macht in 1.9.2 nichts. Das Erfordernis eines Edelsteins ist einfach und ich muss keine Codefragmente in mein Projekt werfen.

Fügen Sie es Ihrem Gemfile hinzu:

gem "rbx-require-relative"

Dann require 'require_relative'vor dir require_relative.

Zum Beispiel sieht eine meiner Testdateien folgendermaßen aus:

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

Dies ist die sauberste Lösung unter diesen IMOs, und der Edelstein ist nicht so schwer wie Backports.

Edward Anderson
quelle
4

Das backports Edelstein ermöglicht nun das individuelle Laden von Backports.

Sie könnten dann einfach:

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

Dies requirehat keine Auswirkungen auf neuere Versionen und aktualisiert auch keine anderen integrierten Methoden.

Marc-André Lafortune
quelle
3

Eine andere Möglichkeit besteht darin, dem Interpreter mitzuteilen, welche Pfade gesucht werden sollen

ruby -I /path/to/my/project caller.rb
Eradman
quelle
3

Ein Problem, auf das ich bei den auf __FILE__ basierenden Lösungen nicht hingewiesen habe, ist, dass sie in Bezug auf Symlinks nicht funktionieren. Sagen Sie zum Beispiel, ich habe:

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

Das Hauptskript, der Einstiegspunkt, die Anwendung ist foo.rb. Diese Datei ist mit ~ / Scripts / foo verknüpft, das sich in meinem $ PATH befindet. Diese require-Anweisung ist fehlerhaft, wenn ich 'foo' ausführe:

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

Da __FILE__ ~ / Scripts / foo ist, sucht die obige Anweisung require nach ~ / Scripts / foo / lib / someinclude.rb, das offensichtlich nicht existiert. Die Lösung ist einfach. Wenn __FILE__ eine symbolische Verknüpfung ist, muss sie dereferenziert werden. Der Pfadname # realpath hilft uns in dieser Situation:

erfordern "Pfadname"
erfordern File.join (File.dirname (Pathname.new (__ FILE __). realpath), "lib / someinclude")
jptros
quelle
2

Wenn Sie einen Edelstein bauen würden, würden Sie den Ladepfad nicht verschmutzen wollen.

Bei einer eigenständigen Anwendung ist es jedoch sehr praktisch, das aktuelle Verzeichnis wie in den ersten beiden Beispielen zum Ladepfad hinzuzufügen.

Meine Stimme geht an die erste Option auf der Liste.

Ich würde gerne solide Ruby Best Practices-Literatur sehen.

Casey Watson
quelle
1
Betreff: "Ich würde gerne solide Ruby Best Practices-Literatur sehen." Sie können Gregory Browns Ruby Best Practices herunterladen . Sie können auch die Rails Best Practices-Website besuchen .
Michael Stalker
1

Ich würde meine eigene definieren, relative_requirewenn sie nicht existiert (dh unter 1.8) und dann überall dieselbe Syntax verwenden.

Phrogz
quelle
0

Ruby on Rails Weg:

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