Double-Load Guards in Ruby
If you’ve ever worked with C or C++ you no doubt remember that one of continual headaches of working with those languages is avoiding double-inclusions of header files. Most C headers start and end with preprocessor directives in order to avoid this scenario:
#ifndef HELLO_H #define HELLO_H /* ... C code ... */ #endif
At first the situation in Ruby seems much improved. We have require, after all, which ensures that a given file will only be loaded once. Or does it?
As it turns out all is not rosy in Ruby-land. Require works great for gems and system libraries. But when we start loading files relative to the current file, the old double-load problem rears it’s ugly head once more.
Let’s take a look at why this happens. First, we’ll define a file to load:
# foo.rb class Foo puts "I'm being loaded!" end
Now we’ll define a client file which requires foo.rb:
# client.rb require File.join(File.dirname(__FILE__), './foo') require File.join(File.dirname(__FILE__), '../lib/foo')
When we run the script above, we get the following output:
I'm being loaded! I'm being loaded!
Of course, we’d never write a file like that, with the same library being required twice using two different paths. But in larger projects it is all too common for a file to be required using a different path in different files. Because Ruby does not use canonicalized pathnames to check if it has already loaded a file, it assumes that the different paths must refer to different files and loads the file over and over again.
Is this a problem? Besides for slower application startup, the most common ill effect of repeated file loads is constant redefinition warnings. If you have a project that outputs a lot of warnings that look like this on startup…
../lib/foo.rb:1: warning: already initialized constant FOO
…you probably have some files being loaded twice or more times.
More serious and subtle side-effects of double-loading can occur though, especially if the files being reloaded do any class-level metaprogramming. Errors caused by double-loading can be strange and very difficult to track down.
So what do do? Well, first we need to find where the offending loads are originating from. In a large project this can be a daunting task. Here’s some code I wrote to help track down double loads at Devver:
ROOT_PATH = File.expand_path('..', File.dirname(__FILE__))
def require_with_reload_check(raw_path)
unless $LOADED_FEATURES.include?(raw_path)
$require_sites ||= {}
site, line, info = caller.first.split(':')
expanded_site = File.expand_path(site)
load_dir = $LOAD_PATH.detect{|dir|
File.exist?(File.expand_path(raw_path + ".rb", dir))
}
expanded_path = File.expand_path(raw_path, load_dir)
if (expanded_path.index(ROOT_PATH) == 0) &&
$require_sites.key?(expanded_path) &&
$require_sites[expanded_path][:as] != raw_path &&
expanded_path !~ /test_helper$/
warn "!" * 80
warn "#{expanded_path} is being reloaded!"
warn "It was originally loaded as: #{$require_sites[expanded_path][:as]}"
warn "From #{$require_sites[expanded_path][:in]}"
warn "But now it is being loaded as: #{raw_path}"
warn "In #{expanded_site}"
warn "!" * 80
end
$require_sites[expanded_path] = {
:as => raw_path,
:in => expanded_site
}
end
end
unless defined?($reload_guard_enabled)
alias require_without_reload_check require
alias require require_with_reload_check
$reload_guard_enabled = true
end
This code should be loaded as early as possible in your project. Once loaded, it spits out some a very noisy warning every time a file is re-loaded using a different path:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /home/avdi/articles/double-load-guards/lib/foo is being reloaded! It was originally loaded as: ././foo From /home/avdi/articles/double-load-guards/lib/client.rb But now it is being loaded as: ./../lib/foo In /home/avdi/articles/double-load-guards/lib/client.rb !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
But how do we avoid reloads, once we have located the offenders? The simplest remedy is to always expand relative paths before requiring them. I prefer to use the two-argument form of File.expand() to construct fully-qualified paths:
# client.rb
require File.expand_path('../lib/foo', File.dirname(__FILE__))
Eliminate your double-loads, and your Ruby code will load faster, produce fewer warnings, and be that much less prone to bugs.

This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.