In Progress
Unit 1, Lesson 1
In Progress

Relative Require

Video transcript & code

Recently we've been talking about loading and requiring files. I thought today we might continue that theme and discuss requiring files relative to each other.

Here's a project. It contains a file of helper methods, called cow_helpers.rb. It contains a web application written using the sinatra framework in the web.rb file. It also has a bin directory for command-line executables, within which there is a script called hello.

$ tree myapp/
myapp/
├── bin
│   └── hello
├── cow_helpers.rb
└── web.rb

The web app and the command line executable both depend on the cow_helpers.rb file, so each one needs require it. Let's start with the script file.

We can't simply require cow_helpers by name, because it's not in the load path.

require "cow_helpers"

And of course requiring the file's absolute pathname isn't an option, since then we'd never be able to move the project to another directory.

One thing that might occur to us is to pass a relative pathname to require. Since this file resides in the bin subdirectory, we would require ../cow_helpers.

require "../cow_helpers"

When we switch to the bin directory and execute the file, this seems to work fine.

$ cd myapp/bin
$ ./hello
 ______________
< Hello, world >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

But when we cd up to the project root and try to execute the script from there, things start to go sideways.

$ cd ..
$ bin/hello
/home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- ../cow_helpers (LoadError)
        from /home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55:in `require'
        from bin/hello:3:in `<main>'

The problem here is that when we pass a relative path to require, it doesn't consider the path to be relative to the current file. Instead it's considered to be relative to the current working directory. As a result, requiring a relative path will never work reliably unless we are guaranteed to only run the program from a single working directory.

What we need is a way to require a file relative to the current file, not the present working directory. One way we might do that is to leverage the special __FILE__ variable. (BTW, if you're wondering what I just said, "dunder" is a verbal shorthand for "double underscore" that originated in the Python community.)

The __FILE__ variable always contains the absolute path of the file it is found in. We can use it along with File.expand_path to get the path we need.

The first argument to File.expand_path is the path to be expanded, and the second is the base path to start from. Let's start with a simple ... .. says to go up one directory. Since our starting path is the full filename of the current file, the result is that the file's basename and extension is stripped off, leaving the file's directory.

If we add a second .., we wind up in the app's root directory.

To get the final path we need, we then append the basename of the file to be required. This gives us a fully-qualified path to the helper file, independent of the current working directory.

#!/usr/bin/env ruby

__FILE__
# => /myapp/bin/hello

File.expand_path "..", __FILE__
# => /myapp/bin

File.expand_path "../..", __FILE__
# => /myapp

File.expand_path "../../cow_helpers", __FILE__
# => /myapp/cow_helpers

require File.expand_path("../../cow_helpers", __FILE__)

print CowHelpers.moo "Hello, world"

We can take this whole expression and put it in front of require, and we now have a more robust require statement. We can now execute our script from inside the bin directory, or from the project root directory, and it will work either way.

$ ./hello
 ______________
< Hello, world >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$ cd ..
$ bin/hello
 ______________
< Hello, world >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

It may be more robust, but it's also a lot more verbose. We don't want to have to write all this out every time we require a file relative to the current one.

Which is why in version 1.9.2, Ruby's require gained a new cousin: require_relative. This Kernel method does exactly what its name suggests: we can give it a path relative to the directory of the current file, and it will require the file corresponding to that path. No expanding paths, and no __FILE__ variable needed.

We can use require_relative in both our script file and in our sinatra app, adjusting the relative path as needed. Note that in the sinatra app, we can just use the bare basename of the file to be required. There is no need to prefix it with ./.

#!/usr/bin/env ruby

require_relative "../cow_helpers"

print CowHelpers.moo "Hello, world"
require "sinatra"
require_relative "cow_helpers"

get "/" do
  text = params["text"] || "Hello"
  content_type "text/plain"
  CowHelpers.moo(text)
end

When we need to require a Ruby source file relative to the current file, require_relative is the way to go. Unfortunately, it's not all sunshine and rainbows. require_relative also has some serious drawbacks, which we'll discuss in an upcoming episode. But for now: happy hacking!

Responses