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:

C

1

2

3

4

5

6

#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:

Ruby

1

2

3

4

# foo.rb

classFoo

puts"I'm being loaded!"

end

Now we’ll define a client file which requires foo.rb:

Ruby

1

2

3

# client.rb

requireFile.join(File.dirname(__FILE__),'./foo')

requireFile.join(File.dirname(__FILE__),'../lib/foo')

When we run the script above, we get the following output:

TT

1

2

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…

TT

1

../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:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

ROOT_PATH=File.expand_path('..',File.dirname(__FILE__))

defrequire_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

unlessdefined?($reload_guard_enabled)

aliasrequire_without_reload_checkrequire

aliasrequirerequire_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:

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:

Ruby

1

2

# client.rb

requireFile.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.