Smart Requires in Ruby

I have a lot of Ruby RSpec files that start out with a line something like this:

 require File.join(File.dirname(__FILE__), %w[.. spec_helper])

“This is boilerplate” thought I one day, “my editor should insert this line for me!” But there’s a problem: the line must change depending on how deep in the directory hierarchy the file is found. E.g.:

 require File.join(File.dirname(__FILE__), %w[.. .. .. spec_helper])

This is Ruby, though. Surely there is a concise way to dynamically locate a file in a parent directory? As it turns out, there is. Here’s my solution:

  require 'pathname'
  require Pathname(__FILE__).ascend{|d| h=d+'spec_helper.rb'; break h if h.file?}

OK, it’s two lines instead of one. But the advantage is, now I can insert those two lines into my standard editor template for *_spec.rb files. And it’ll Just Work so long as there is a spec_helper.rb somewhere in the file’s parent directories.

How it works:

  • Pathname#ascend iterates backwards up a file path, successively removing path elements.
  • Pathname#+ joins two path elements using the path separator character (e.g. /).
  • break is called with an argument, causing the block to return the matching path.

6 comments

  1. Keep going! Good idea, but it can be made even prettier:

    <br>require 'simple_require'<br># this library would encapsulates the pathname stuff, ascend, etc. so we could then do this:<br>simple_require('spec_helper', 'some_other_file')<br>

    1. The idea is to have no dependencies apart form the Ruby stdlib. It's one of those chicken-and-egg things – usually it's the spec_helper.rb that requires rubygems et. al.

  2. For spec_helper, it's even easier to go:

    require 'spec_helper'

    And then modify the load path to contain the load path to contain the spec or test directory.

    This is a practice Rails adopted at one point. In addition to simplifying the require in your specs, it also prevents spec_helper.rb from being required multiple times.

    Imagine two specs, spec/foo_spec.rb and spec/bar/baz_spec.rb. foo_spec would end up requiring 'spec_helper.rb' and bar/baz_spec.rb would end up requiring baz/../spec_helper.rb

    Normally, ruby only requires a file once… but, it's not smart enough to understand that spec_helper and baz/../spec_helper.rb. The result is that it gets required multiple times. This mostly is a problem if you define constants (you'll see warnings about constant already defined) or if you include/extend modules that do something with their callbacks (rr causes a system stack too deep if you include its module more than once).

    1. Yeah, multiple requires because of different paths to the same file is something I've had a lot of trouble with at my day job. I agree that when it's practical, altering the load path and sticking with unqualified requires is the way to go.

      I'm not sure I prefer that for specs, though, because I've gotten used to always being able to run my specs as 'ruby my_spec.rb' – no rake, no script/spec, no spec command. This works well for e.g. Emacs “run this file” keybindings – it means the editor doesn't need to any special sniffing the file to determine what command to run the file with.

      I've long wished the Ruby interpreter would just normalize and absolutize all require-d filenames before loading them.

  3. For spec_helper, it's even easier to go:

    require 'spec_helper'

    And then modify the load path to contain the load path to contain the spec or test directory.

    This is a practice Rails adopted at one point. In addition to simplifying the require in your specs, it also prevents spec_helper.rb from being required multiple times.

    Imagine two specs, spec/foo_spec.rb and spec/bar/baz_spec.rb. foo_spec would end up requiring 'spec_helper.rb' and bar/baz_spec.rb would end up requiring baz/../spec_helper.rb

    Normally, ruby only requires a file once… but, it's not smart enough to understand that spec_helper and baz/../spec_helper.rb. The result is that it gets required multiple times. This mostly is a problem if you define constants (you'll see warnings about constant already defined) or if you include/extend modules that do something with their callbacks (rr causes a system stack too deep if you include its module more than once).

  4. Yeah, multiple requires because of different paths to the same file is something I've had a lot of trouble with at my day job. I agree that when it's practical, altering the load path and sticking with unqualified requires is the way to go.

    I'm not sure I prefer that for specs, though, because I've gotten used to always being able to run my specs as 'ruby my_spec.rb' – no rake, no script/spec, no spec command. This works well for e.g. Emacs “run this file” keybindings – it means the editor doesn't need to any special sniffing the file to determine what command to run the file with.

    I've long wished the Ruby interpreter would just normalize and absolutize all require-d filenames before loading them.

Leave a Reply to avdi Cancel reply

Your email address will not be published. Required fields are marked *