Following the Path

Joël Quenneville
require "user"

Where is the user.rb file located? Perhaps in the current directory? That’s a trick question. There isn’t enough information to determine that from that single line.

The answer is more complex, flexible, and involves some UNIX history.

$LOAD_PATH

$LOAD_PATH is a global variable in Ruby that points to an array of path strings. The following is the (truncated) result of viewing $LOAD_PATH in pry:

[1] pry(main)> $LOAD_PATH
=>
["/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/did_you_mean-1.0.0/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/slop-3.6.0/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/method_source-0.8.2/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/pry-0.10.4/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/coderay-1.1.1/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/pry-byebug-3.4.0/lib",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/byebug-9.0.6/lib",
...skipping...
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/vendor_ruby/2.3.0",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/vendor_ruby/2.3.0/x86_64-darwin14",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/vendor_ruby",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/2.3.0",
 "/Users/joelquenneville/.rbenv/versions/2.3.1/lib/ruby/2.3.0/x86_64-darwin14"]

The paths are mostly going to various gem’s lib/ directories or Ruby installations. When executing require "user, Ruby looks for a file named user.rb in all of these locations.

What if there’s a user.rb in several of the locations? Ruby will load the first one it finds.

Gems will automatically add themselves to your load path.

Custom load path

That’s all great but what about adding your own paths? This can be accomplished by mutating the $LOAD_PATH. Using Array#unshift will prepend your path:

$LOAD_PATH.unshift File.expand_path(".", "lib")

Now require "user" will first look for ./lib/user.rb.

Complex paths

The same logic applies to more complex requires:

require "/api/client"

would look for /api/client.rb in all of the paths, starting with ./lib/api/client.rb

UNIX

UNIX systems take a very similar approach with the environment variable $PATH. A (truncated) path might look like:

$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/texbin:/usr/local/sbin

$PATH is a list of paths separated by colons. When you type a command into the shell, it looks at all these locations for an executable matching the name you typed and will execute the first one it finds.

This is why some tool installation guides ask you to modify $PATH in your shell config.

You can see which path your shell is using for a given command with which

$ which ls
/bin/ls

Sometimes you will have multiple versions of a command installed. For example psql from Homebrew and psql from Postgres.app. The which command is great for finding out which one is being used. Use this info to tweak your $PATH to ensure the right one loads. You can also use the -a flag on which to show all installed executables:

% which -a psql
/Applications/Postgres.app/Contents/Versions/latest/bin/psql
/usr/local/bin/psql

Wandering off the path

Ruby has another way of requiring code:

require_relative "./lib/user"

Now where is user.rb located? This doesn’t use $LOAD_PATH. Instead, it looks for ./lib/user.rb relative to the location of the current file.

Both require and require_relative take a path to a Ruby file without the .rb extension.

A few UNIX shell commands are hard-coded into the shell and don’t use the $PATH lookup system. For example, where is the which executable?

$ which which
which: shell built-in command

Now you know a bit more about how Ruby locates files, and explored a bit of UNIX history.