File.expand_path("../../Gemfile", __FILE__) How does this work? Where is the file?
ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
I'm just trying to access a .rb file from the some directory and a tutorial is telling me to use this code but I don't see how it is finding the gem file.
File.expand_path('../../Gemfile', __FILE__)
is a somewhat ugly Ruby idiom for getting the absolute path to a file when you know the path relative to the current file. Another way of writing it is this:
File.expand_path('../Gemfile', File.dirname(__FILE__))
both are ugly, but the first variant is shorter. The first variant is, however, also very non-intuitive until you get the hang of it. Why the extra ..
? (but the second variant may give a clue as to why it is needed).
This is how it works: File.expand_path
returns the absolute path of the first argument, relative to the second argument (which defaults to the current working directory). __FILE__
is the path to the file the code is in. Since the second argument in this case is a path to a file, and File.expand_path
assumes a directory, we have to stick an extra ..
in the path to get the path right. This is how it works:
File.expand_path
is basically implemented like this (in the following code path
will have the value of ../../Gemfile
and relative_to
will have the value of /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
(there's a little bit more to it, it expands ~
to the home directory and so on -- there are probably also some other issues with the code above)
Stepping through a call to the code above absolute_path
will first get the value /path/to/file.rb/../../Gemfile
, then for each round in the loop the first ..
will be removed, along with the path component before it. First /file.rb/..
is removed, then on the next round /to/..
is removed, and we get /path/Gemfile
.
To make a long story short, File.expand_path('../../Gemfile', __FILE__)
is a trick to get the absolute path of a file when you know the path relative to the current file. The extra ..
in the relative path is to eliminate the name of the file in __FILE__
.
In Ruby 2.0 there is a Kernel
function called __dir__
that is implemented as File.dirname(File.realpath(__FILE__))
.
Two references:
- File::expand_path method documentation
- How does
__FILE__
work in Ruby
I stumbled across this today:
boot.rb commit in the Rails Github
If you go up two directories from boot.rb in the directory tree:
/railties/lib/rails/generators/rails/app/templates
you see Gemfile, which leads me to believe that File.expand_path("../../Gemfile", __FILE__)
references following file: /path/to/this/file/../../Gemfile