File.expand_path (“../../ Gemfile”, __FILE__) Como isso funciona? Onde está o arquivo?

84

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

Estou apenas tentando acessar um arquivo .rb de algum diretório e um tutorial está me dizendo para usar este código, mas não vejo como ele encontra o arquivo gem.

Thenengah
fonte
1
Consulte também a pergunta stackoverflow.com/questions/4333286
Theo

Respostas:

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

é um idioma Ruby um tanto feio para obter o caminho absoluto para um arquivo quando você conhece o caminho relativo ao arquivo atual. Outra maneira de escrever é esta:

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

ambos são feios, mas a primeira variante é mais curta. A primeira variante, no entanto, também é muito não intuitiva até que você pegue o jeito. Por que o extra ..? (mas a segunda variante pode dar uma pista de por que é necessário).

Funciona assim: File.expand_pathretorna o caminho absoluto do primeiro argumento, relativo ao segundo argumento (que é padronizado para o diretório de trabalho atual). __FILE__é o caminho para o arquivo em que o código está. Como o segundo argumento, neste caso, é um caminho para um arquivo e File.expand_pathassume um diretório, temos que inserir um item extra ..no caminho para obter o caminho correto. É assim que funciona:

File.expand_pathé basicamente implementado assim (no código a seguir pathterá o valor de ../../Gemfilee relative_toterá o valor de /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

(há um pouco mais, ele se expande ~para o diretório inicial e assim por diante - provavelmente também há alguns outros problemas com o código acima)

Percorrer uma chamada para o código acima absolute_pathobterá primeiro o valor e /path/to/file.rb/../../Gemfile, em seguida, para cada rodada no loop, a primeira ..será removida, junto com o componente de caminho antes dela. Primeiro /file.rb/..é removido, então na próxima rodada /to/..é removido e nós obtemos /path/Gemfile.

Para encurtar a história, File.expand_path('../../Gemfile', __FILE__)é um truque para obter o caminho absoluto de um arquivo quando você conhece o caminho relativo ao arquivo atual. O extra ..no caminho relativo é eliminar o nome do arquivo em __FILE__.

No Ruby 2.0, existe uma Kernelfunção chamada __dir__que é implementada como File.dirname(File.realpath(__FILE__)).

Theo
fonte
2
Existe alguma razão pela qual você não deve apenas usar 'require_relative' diferente de incompatibilidade com Ruby 1.9.2 anterior?
Danny Andrews
9
A partir do Ruby 2.0, você pode usarFile.expand_path('../Gemfile',__dir__)
Phrogz
Esta linha de Theo finalmente conseguiu clicar para mim File.expand_path assumes a directory, embora __FILE__não seja um diretório. Para que as coisas façam sentido, use o __dir__que na verdade é um diretório.
mbigras
9

Duas referências:

  1. Documentação do método File :: expand_path
  2. Como __FILE__funciona em Ruby

Me deparei com isso hoje:

boot.rb commit no Rails Github

Se você subir dois diretórios de boot.rb na árvore de diretórios:

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

você vê Gemfile, o que me leva a acreditar que faz File.expand_path("../../Gemfile", __FILE__)referência ao seguinte arquivo:/path/to/this/file/../../Gemfile

Patrick Klingemann
fonte
Obrigado pela postagem, mas veio do tutorial do gembundler, então eu estava tentando entender exatamente como eles tinham :)
thenengah