File.expand_path («../../ Gemfile», __FILE__) Как это работает? Где файл?

84

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

Я просто пытаюсь получить доступ к файлу .rb из некоторого каталога, и в учебном руководстве мне предлагается использовать этот код, но я не понимаю, как он находит файл драгоценного камня.

Thenengah
источник
1
Также см. Вопрос stackoverflow.com/questions/4333286
Тео

Ответы:

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

это несколько уродливая идиома Ruby для получения абсолютного пути к файлу, когда вы знаете путь относительно текущего файла. Другой способ записать это:

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

оба уродливы, но первый вариант короче. Однако первый вариант также очень неинтуитивен, пока вы не освоите его. Почему лишнее ..? (но второй вариант может подсказать, зачем он нужен).

Вот как это работает: File.expand_pathвозвращает абсолютный путь первого аргумента относительно второго аргумента (который по умолчанию соответствует текущему рабочему каталогу). __FILE__- это путь к файлу, в котором находится код. Поскольку второй аргумент в этом случае - это путь к файлу и File.expand_pathпредполагает наличие каталога, мы должны добавить в путь дополнительный аргумент, ..чтобы получить правильный путь. Вот как это работает:

File.expand_pathв основном реализован следующим образом (в следующем коде pathбудет иметь значение ../../Gemfileи relative_toбудет иметь значение /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

(есть ~еще кое- что, он расширяется до домашнего каталога и так далее - вероятно, есть и другие проблемы с приведенным выше кодом)

При пошаговом вызове приведенного выше кода absolute_pathсначала будет получено значение /path/to/file.rb/../../Gemfile, затем для каждого раунда в цикле первый ..будет удален вместе с компонентом пути перед ним. Сначала /file.rb/..снимается, потом на следующем раунде /to/..снимается, и мы получаем /path/Gemfile.

Короче говоря, File.expand_path('../../Gemfile', __FILE__)это уловка, позволяющая получить абсолютный путь к файлу, когда вы знаете путь относительно текущего файла. Дополнительное значение ..в относительном пути - исключить имя файла в __FILE__.

В Ruby 2.0 есть Kernelфункция, __dir__которая реализована как File.dirname(File.realpath(__FILE__)).

Тео
источник
2
Есть ли какая-либо причина, по которой вы не должны просто использовать require_relative, кроме несовместимости с до Ruby 1.9.2?
Дэнни Эндрюс
9
Начиная с Ruby 2.0 вы можете использоватьFile.expand_path('../Gemfile',__dir__)
Phrogz
Эта строка от Тео наконец-то заставила меня щелкнуть по ней File.expand_path assumes a directory, хотя __FILE__это и не каталог. Чтобы вещи имели смысл использовать, __dir__который на самом деле является каталогом.
mbigras
9

Две ссылки:

  1. Документация по методу File :: expand_path
  2. Как __FILE__работает в Ruby

Сегодня я наткнулся на это:

boot.rb коммит в Rails Github

Если вы перейдете на два каталога вверх от boot.rb в дереве каталогов:

/ Railties / lib / рельсы / генераторы / рельсы / приложение / шаблоны

вы видите Gemfile, что заставляет меня думать, что он File.expand_path("../../Gemfile", __FILE__)ссылается на следующий файл:/path/to/this/file/../../Gemfile

Патрик Клингеманн
источник
Спасибо за пост, но это пришло из учебника по гембандлерам, поэтому я пытался понять, как именно они это сделали :)
thenengah