File.expand_path („../../ Gemfile”, __FILE__) Jak to działa? Gdzie jest plik?

84

ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)

Próbuję tylko uzyskać dostęp do pliku .rb z jakiegoś katalogu, a samouczek mówi mi, żebym użył tego kodu, ale nie widzę, jak znajduje plik klejnotu.

thenengah
źródło
1
Zobacz także pytanie stackoverflow.com/questions/4333286
Theo

Odpowiedzi:

194
File.expand_path('../../Gemfile', __FILE__)

jest nieco brzydkim idiomem Rubiego do pobierania bezwzględnej ścieżki do pliku, gdy znasz ścieżkę względną do bieżącego pliku. Innym sposobem zapisu jest to:

File.expand_path('../Gemfile', File.dirname(__FILE__))

oba są brzydkie, ale pierwszy wariant jest krótszy. Pierwszy wariant jest jednak również bardzo nieintuicyjny, dopóki go nie opanujesz. Dlaczego ekstra ..? (ale drugi wariant może dać wskazówkę, dlaczego jest potrzebny).

Oto jak to działa: File.expand_pathzwraca bezwzględną ścieżkę do pierwszego argumentu względem drugiego argumentu (który domyślnie wskazuje bieżący katalog roboczy). __FILE__to ścieżka do pliku, w którym znajduje się kod. Ponieważ drugi argument w tym przypadku jest ścieżką do pliku i File.expand_pathzakłada katalog, musimy wstawić dodatkową ..ścieżkę, aby uzyskać właściwą ścieżkę. Tak to działa:

File.expand_pathjest zasadniczo zaimplementowany w ten sposób (w poniższym kodzie pathbędzie miał wartość ../../Gemfilei relative_tobędzie miał wartość /path/to/file.rb):

def File.expand_path(path, relative_to=Dir.getwd)
  # first the two arguments are concatenated, with the second argument first
  absolute_path = File.join(relative_to, path)
  while absolute_path.include?('..')
    # remove the first occurrence of /<something>/..
    absolute_path = absolute_path.sub(%r{/[^/]+/\.\.}, '')
  end
  absolute_path
end

(jest w tym trochę więcej, rozwija się ~do katalogu domowego i tak dalej - prawdopodobnie są też inne problemy z powyższym kodem)

Przejście przez wywołanie powyższego kodu absolute_pathnajpierw spowoduje pobranie wartości /path/to/file.rb/../../Gemfile, a następnie dla każdej rundy w pętli pierwsza ..zostanie usunięta wraz ze składnikiem ścieżki przed nią. Najpierw /file.rb/..jest usuwany, a następnie w następnej rundzie /to/..jest usuwany i otrzymujemy /path/Gemfile.

Krótko mówiąc, File.expand_path('../../Gemfile', __FILE__)jest sztuczką polegającą na uzyskaniu bezwzględnej ścieżki do pliku, gdy znasz ścieżkę względną do bieżącego pliku. Dodatkowym ..elementem ścieżki względnej jest wyeliminowanie nazwy pliku w formacie __FILE__.

W Ruby 2.0 istnieje Kernelfunkcja o nazwie, __dir__która jest zaimplementowana jako File.dirname(File.realpath(__FILE__)).

Theo
źródło
2
Czy jest jakiś powód, dla którego nie powinieneś po prostu używać 'require_relative' innego niż niezgodność z wersją wcześniejszą Ruby 1.9.2?
Danny Andrews
9
Od Ruby 2.0 możesz używaćFile.expand_path('../Gemfile',__dir__)
Phrogz
W tym wierszu Theo w końcu udało mi się kliknąć File.expand_path assumes a directory, mimo że __FILE__nie jest to katalog. Aby rzeczy miały sens, użyj tego, __dir__który w rzeczywistości jest katalogiem.
mbigras
9

Dwie referencje:

  1. Dokumentacja metody File :: expand_path
  2. Jak __FILE__działa w Rubim

Natknąłem się na to dzisiaj:

boot.rb commit w Rails Github

Jeśli przejdziesz do dwóch katalogów w górę z boot.rb w drzewie katalogów:

/ railties / lib / rails / generators / rails / app / templates

widzisz Gemfile, co prowadzi mnie do wniosku, że File.expand_path("../../Gemfile", __FILE__)odwołuje się do następującego pliku:/path/to/this/file/../../Gemfile

Patrick Klingemann
źródło
Dzięki za post, ale to pochodzi z tutoriala Gembundler, więc starałem się zrozumieć, jak dokładnie to mieli :)
thenengah