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?








6 comments
I think this behaviour was changed in ruby 1.9.
January 09, 2008 at 06:52 AM
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.
January 09, 2008 at 01:52 PM
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.
January 19, 2008 at 12:32 AM
James,
I think you have that backwards. From the Ruby API docs for
#requireAlso, neither
#requireor#loadbehave differently based on if the file is modified.January 20, 2008 at 08:23 PM
This is fixed in 1.9. WIll be very nice to drop the nasty expand_path everywhere.
April 22, 2008 at 08:56 PM
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/
May 27, 2008 at 09:03 PM
Speak your mind: