89

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.

thenengah
  • 42,557
  • 33
  • 113
  • 157

2 Answers2

201
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__)).

Theo
  • 131,503
  • 21
  • 160
  • 205
  • 2
    Is there any reason why you shouldn't just use 'require_relative' other than incompatibility with pre Ruby 1.9.2? – Danny Andrews Jan 23 '15 at 16:33
  • 9
    As of Ruby 2.0 you can use `File.expand_path('../Gemfile',__dir__)` – Phrogz Jan 04 '16 at 03:00
  • This line from Theo finally got it to click for me `File.expand_path assumes a directory`, even though `__FILE__` is not a directory. For things to make sense use `__dir__` which actually is a directory. – mbigras Mar 24 '18 at 03:43
9

Two references:

  1. File::expand_path method documentation
  2. 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

Community
  • 1
  • 1
Patrick Klingemann
  • 8,884
  • 4
  • 44
  • 51
  • Thanks for the post, but this came from the gembundler tutorial so I was trying to understand it exactly how they had it :) – thenengah Dec 17 '10 at 22:33