Is this your first visit? You may want to subscribe to the feed.

Ruby's require doesn't expand paths

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?

Code: rspec, ruby Jan 08, 2008 ● updated Jan 08, 2008 7 comments

7 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
  7. Here’s my hack at a utility to avoid this problem in 1.8.x: http://github.com/rogerdpack/roger-useful/tree/a3c4867cd3eef0f6f088e88f1218184cb21a6803/unique_require Enjoy! -=r

    Roger Roger February 20, 2009 at 07:26 PM

Speak your mind:

*

*


* I hate spam and will never sell or publish your email address.

(You may use textile in your comments.)

Subscribe

Browse by Tag