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
$ 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.
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
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.)
__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!