Ruby: require vs require_relative - najlepsza praktyka obejścia działania w Ruby <1.9.2 i> = 1.9.2


153

Jaka jest najlepsza praktyka, jeśli chcę requirepliku względnej w Ruby i chcę go do pracy zarówno 1.8.x oraz> = 1.9.2?

Widzę kilka opcji:

  • po prostu zrób $LOAD_PATH << '.'i zapomnij o wszystkim
  • robić $LOAD_PATH << File.dirname(__FILE__)
  • require './path/to/file'
  • sprawdź, czy RUBY_VERSION<1.9.2, a następnie zdefiniuj require_relativejako require, użyj require_relativepóźniej wszędzie tam, gdzie jest to potrzebne
  • sprawdź, czy require_relativejuż istnieje, jeśli tak, spróbuj postępować jak w poprzednim przypadku
  • używają dziwnych konstrukcji, takich jak - niestety nie wydaje się, aby działały w pełni w Rubim 1.9, ponieważ na przykład:
    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>'
  • Jeszcze dziwniejsza konstrukcja: wydaje się działać, ale jest dziwna i niezbyt ładna.
    require File.join(File.expand_path(File.dirname(__FILE__)), 'path/to/file')
  • Użyj klejnotów backportów - jest trochę ciężki, wymaga infrastruktury rubygemów i zawiera mnóstwo innych obejść, podczas gdy ja chcę po prostu requirepracować z plikami względnymi.

W StackOverflow jest blisko powiązane pytanie, które zawiera więcej przykładów, ale nie daje jasnej odpowiedzi - co jest najlepszą praktyką.

Czy istnieje jakieś przyzwoite, akceptowane przez wszystkich uniwersalne rozwiązanie, które sprawi, że moja aplikacja będzie działać na Rubim <1.9.2 i> = 1.9.2?

AKTUALIZACJA

Wyjaśnienie: nie chcę tylko odpowiedzi typu „możesz zrobić X” - w rzeczywistości wspomniałem już o większości opcji, o których mowa. Chcę uzasadnienia , czyli dlaczego jest to najlepsza praktyka, jakie są jej wady i zalety oraz dlaczego należy ją wybrać spośród innych.


3
Cześć, jestem nowy. Czy ktoś mógłby wyjaśnić od początku - jaka jest różnica między requirei require_relative?
Colonel Panic

3
W starszym Ruby 1.8, jeśli uruchomiłeś plik a.rbi chciałeś, aby interpreter odczytywał i parsował zawartość pliku b.rbw bieżącym katalogu (zwykle w tym samym katalogu co w a.rb), po prostu napisz require 'b'i będzie dobrze, ponieważ domyślna ścieżka wyszukiwania zawiera bieżący katalog. W bardziej nowoczesnym Ruby 1.9 będziesz musiał pisać require_relative 'b'w tym przypadku, tak jakbyś require 'b'szukał tylko w standardowych ścieżkach bibliotek. To jest coś, co łamie kompatybilność do przodu i do tyłu dla prostszych skryptów, które nie będą poprawnie instalowane (na przykład same skrypty instalacyjne ).
GreyCat

Możesz teraz użyć backportstylko dla require_relative, zobacz moją odpowiedź ...
Marc-André Lafortune

Odpowiedzi:


64

Obejście tego problemu zostało właśnie dodane do klejnotu „aws”, więc pomyślałem, że udostępnię, ponieważ zainspirował go ten 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

Pozwala to na użycie require_relativetak, jak w Ruby 1.9.2 w Ruby 1.8 i 1.9.1.


3
Jak potrzebujesz pliku require_relative.rb? Musisz wymagać require_relative.rb, a następnie require_relative pozostałych wymagań. A może coś mi brakuje?
ethicalhack3r

7
Ta require_relativefunkcja jest zawarta w projekcie rozszerzenia podstawowych bibliotek Ruby, które można znaleźć tutaj: rubyforge.org/projects/extensions. Powinno być możliwe ich zainstalowanie gem install extensions. Następnie w swoim kodzie dodaj następujący wiersz przed require_relative: require 'extensions / all' (pochodzi z postu Aurril tutaj )
thegreendroid

@ ethicalhack3r po prostu skopiuj i wklej ten kod na początku skryptu ruby ​​lub, jeśli jest w railsach, wrzuć go na górę environment.rb lub coś w tym stylu.
Travis Reeder

46

Zanim wykonałem skok do 1.9.2, użyłem następujących dla wymagań względnych:

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

To trochę dziwne, gdy widzisz go po raz pierwszy, ponieważ wygląda na to, że na początku jest dodatkowe „…”. Powodem jest to, że expand_pathrozszerzy ścieżkę względem drugiego argumentu, a drugi argument zostanie zinterpretowany tak, jakby był katalogiem. __FILE__oczywiście nie jest katalogiem, ale to nie ma znaczenia, ponieważ expand_pathnie obchodzi czy istnieją pliki, czy nie, to po prostu zastosować kilka zasad, aby rozwinąć takie rzeczy .., .i ~. Jeśli uda ci się przejść przez początkową „minutę oczekiwania, czy nie ma tam dodatkowej ..?” Myślę, że powyższa linia działa całkiem nieźle.

Przy założeniu, że __FILE__ to /absolute/path/to/file.rb, co się dzieje, że expand_pathwybuduje ciąg /absolute/path/to/file.rb/../relative/path, a następnie zastosować regułę, która mówi, że ..należy usunąć składnik ścieżki przed nim ( file.rbw tym przypadku), wracając /absolute/path/to/relative/path.

Czy to najlepsza praktyka? Zależy od tego, co przez to rozumiesz, ale wygląda na to, że jest to cała baza kodu Railsów, więc powiedziałbym, że jest to przynajmniej dość powszechny idiom.


1
Ja też to często widzę. Jest brzydki, ale wydaje się, że działa dobrze.
yfeldblum

12
trochę czystsze: wymagaj File.expand_path ('względna / ścieżka', nazwa_pliku_pliku ( PLIK ))
Yannick Wurm

1
Myślę, że nie jest dużo czystszy, po prostu jest dłuższy. Obie są nieznośne jak diabli, a wybierając jedną z dwóch złych opcji, wolę tę, która wymaga mniej pisania.
Theo

6
Wygląda na to, że File.expand_path ('../ relpath.x', File.dirname ( FILE )) jest lepszym idiomem, chociaż jest bardziej szczegółowy. Poleganie na prawdopodobnie zepsutej funkcjonalności ścieżki pliku interpretowanej jako ścieżka do katalogu z dodatkowym nieistniejącym katalogiem może się zepsuć, gdy / jeśli ta funkcja zostanie naprawiona.
jpgeek

1
Być może uszkodzony, ale w systemie UNIX tak było od zawsze. Po prostu nie ma różnicy między katalogiem a plikiem, jeśli chodzi o ścieżki i rozdzielczość „..” - więc nie tracę z tego powodu snu.
Theo

6

Kilof ma do tego fragment dla wersji 1.8. Oto ona:

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

Zasadniczo używa tylko tego, co odpowiedział Theo, ale nadal możesz używać require_relative.


Jak sprawdzić, czy ten fragment powinien zostać aktywowany, czy nie jest prawidłowo? Używając $RUBY_VERSIONlub sprawdzając, czy require_relativeistnieje bezpośrednio?
GreyCat

1
Zawsze kaczka, sprawdź, czy require_relativejest zdefiniowana.
Theo,

@Theo @GreyCat tak, sprawdziłbym, czy jest to potrzebne. Właśnie umieszczałem tutaj fragment kodu, aby ludzie mogli go pokazać. Osobiście i tak użyłbym odpowiedzi Grega, tak naprawdę to publikowałem, ponieważ ktoś o tym wspomniał, nie mając tego sam.
Paul Hoffer,

6
$LOAD_PATH << '.'

$LOAD_PATH << File.dirname(__FILE__)

To nie jest dobry nawyk bezpieczeństwa: dlaczego miałbyś ujawniać cały katalog?

require './path/to/file'

To nie działa, jeśli RUBY_VERSION <1.9.2

używaj dziwnych konstrukcji, takich jak

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

Jeszcze dziwniejsza konstrukcja:

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

Użyj klejnotów backportów - jest trochę ciężki, wymaga infrastruktury rubygemów i zawiera mnóstwo innych obejść, podczas gdy ja po prostu chcę wymagać pracy z plikami względnymi.

Odpowiedziałeś już, dlaczego nie są to najlepsze opcje.

sprawdź, czy RUBY_VERSION <1.9.2, następnie zdefiniuj require_relative jako wymaganie, użyj require_relative wszędzie tam, gdzie jest to potrzebne później

sprawdź, czy require_relative już istnieje, jeśli tak, spróbuj postępować jak w poprzednim przypadku

To może działać, ale istnieje bezpieczniejszy i szybszy sposób: radzenie sobie z wyjątkiem 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

Jestem fanem używania klejnotu rbx-require-względny ( źródło ). Pierwotnie został napisany dla Rubiniusa, ale obsługuje także MRI 1.8.7 i nie robi nic w 1.9.2. Wymaganie klejnotu jest proste i nie muszę wrzucać fragmentów kodu do mojego projektu.

Dodaj go do swojego Gemfile:

gem "rbx-require-relative"

Wtedy require 'require_relative'przed tobą require_relative.

Na przykład jeden z moich plików testowych wygląda następująco:

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

Jest to najczystsze rozwiązanie spośród wszystkich tych IMO, a klejnot nie jest tak ciężki jak backporty.


4

Plik backportsGem pozwala teraz indywidualny załadunek backports.

Możesz wtedy po prostu:

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

Nie requirewpłynie to na nowsze wersje ani nie zaktualizuje żadnych innych wbudowanych metod.


3

Inną opcją jest poinformowanie tłumacza, które ścieżki mają przeszukiwać

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

3

Jednym z problemów, których nie zauważyłem w rozwiązaniach opartych na __FILE__, jest to, że psują się w odniesieniu do dowiązań symbolicznych. Na przykład powiedz, że mam:

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

Główny skrypt, punkt wejścia, aplikacja to foo.rb. Ten plik jest powiązany z ~ / Scripts / foo, który znajduje się w mojej $ PATH. Ta instrukcja wymagania jest zepsuta, kiedy wykonuję 'foo':

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

Ponieważ __FILE__ to ~ / Scripts / foo, więc powyższa instrukcja require szuka ~ / Scripts / foo / lib / someinclude.rb, który oczywiście nie istnieje. Rozwiązanie jest proste. Jeśli __FILE__ jest dowiązaniem symbolicznym, należy go usunąć. Pathname # realpath pomoże nam w tej sytuacji:

wymagaj „nazwy ścieżki”
require File.join (File.dirname (Pathname.new (__ FILE __). realpath), „lib / someinclude”)

2

Gdybyś budował klejnot, nie chciałbyś zanieczyszczać ścieżki ładowania.

Ale w przypadku samodzielnej aplikacji bardzo wygodnie jest po prostu dodać bieżący katalog do ścieżki ładowania, tak jak robisz to w pierwszych 2 przykładach.

Mój głos trafia do pierwszej opcji na liście.

Bardzo chciałbym zobaczyć solidną literaturę dotyczącą najlepszych praktyk Rubiego.


1
Odp .: „Bardzo chciałbym zobaczyć solidną literaturę dotyczącą najlepszych praktyk w języku Ruby”. Możesz pobrać Ruby Best Practices Gregory'ego Browna . Możesz również odwiedzić stronę Rails Best Practices .
Michael Stalker

1

Zdefiniowałbym własną, relative_requiregdyby nie istniała (tj. Poniżej 1.8), a następnie użyłbym wszędzie tej samej składni.


0

Sposób Ruby on Rails:

config_path = File.expand_path("../config.yml", __FILE__)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.