Ruby's require doesn't expand paths

rspec | ruby January 08 2008

I ran across this issue several weeks ago, but it came up at the latest Grand Rapids Ruby Group meeting, so I thought I would share it.

Ruby’s require, for better or worse, doesn’t expand paths. As the docs point out, require 'a'; require './a' will load a.rb twice. This doesn’t matter most of the time, but there’s one place it’s used often that will bite you: tests and specs.

Every Rails’ test has a line similar to this:

require File.dirname(__FILE__) + '/../test_helper'

This doesn’t normally cause any problems, as long as every test has an identical require statement. Where you start to get into problems is when you have tests in a nested subdirectory (like test/controllers/admin/users_controller_test.rb), in which case the require statement would look like this:

require File.dirname(__FILE__) + '/../../test_helper'

require sees this as a different file and will re-load it. This still shouldn’t hurt you unless you’re doing something in test_helper.rb that would be changed by loading it twice (like aliasing a method). This also effects RSpec with requiring spec_helper.rb

The solution? Expand the path yourself.

require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')

It’s not really clear to me why Ruby’s require works this way. You would think a method that was intended to only load a file once would make sure that it never re-loaded the same file, no matter how it was referenced. It definitely doesn’t adhere to the “principle of least surprise”. Any idea why?

posted by brandon | updated January 8th 11:32 PM
comments feed

6 comments

  1. I think this behaviour was changed in ruby 1.9.

    Eric-Olivier Lamey Eric-Olivier Lamey
    January 09, 2008 at 06:52 AM
  2. Another way to solve this is that everyone always does the vanilla

    require File.dirname(FILE) + ’/../test_helper’

    But add a test_helper.rb file (or files, depending on how many sublevels you have) that then includes the right test_helper.rb.

    The advantage to doing it this way is you never have to care how many /../ substrings there are in your paths.

    James Moore James Moore
    January 09, 2008 at 01:52 PM
  3. Another issue is that require is not meant to load a file only once. Load is. If you require a file, change it, and require it again, your changes will be picked up. If you load a file, change it, and load it again, no change. This is on purpose since sometimes you do want to load a file twice.

    James Deville James Deville
    January 19, 2008 at 12:32 AM
  4. James,

    I think you have that backwards. From the Ruby API docs for #require

    The name of the loaded feature is added to the array in $”. A feature will not be loaded if it‘s name already appears in $”.

    Also, neither #require or #load behave differently based on if the file is modified.

    Brandon Brandon
    January 20, 2008 at 08:23 PM
  5. This is fixed in 1.9. WIll be very nice to drop the nasty expand_path everywhere.

    Rob Sanheim Rob Sanheim
    April 22, 2008 at 08:56 PM
  6. I wrote a snippet that automates this, in case you aren’t up for the global search and replace alternative.

    http://www.pervasivecode.com/blog/2008/05/16/rails-snippet-require-app-files-only-once/

    Jamie Flournoy Jamie Flournoy
    May 27, 2008 at 09:03 PM

Speak your mind:

(Required)

(Required)


(You may use textile in your comments.)

About

I'm Brandon Keepers, a web application developer that likes beautiful code, valid markup and adherence to standards. As a part of Collective Idea in Holland, Michigan, I practice Agile software development primarily using Ruby on Rails.

-86.103171 42.785037

Contact:

more »

Syndicate