The require command is not suppossed to load a file that is already loaded. However while requiring a file, ruby VM looks at the path and the file name and it can cause some strange behavior.

Basics

Ruby allows two ways to load file: require and load. load will load a file every single time while require will not load already loaded file.

I have two files here in the same directory: main.rb and foo.rb . Here is what files look like.

  
    #main.rb
    require 'foo'
    require 'foo'
  
  
    #foo.rb
    puts "I am being loaded"
  

If you execute ruby main.rb you will see a single message "I am being loaded". That is because once a file is already loaded then it wil not be loaded second time.

Require using relative path

Now let's change the main.rb to require foo.rb using relative path. After the change file looks like this

  
    #main.rb
    require 'foo'
    require '../dev/foo'
  

If you execute ruby main.rb you will see the puts message twice. That is because ruby is loading foo.rb twice.

$LOADED_FEATURES - keeping track of what's loaded and what's not

ruby uses $LOADED_FEATURES , a global constant, to keep track of what's loaded and what's not loaded. Here is an updated version of main.rb.

  
    #main.rb
    require 'foo'
    require 'foo'
    puts $LOADED_FEATURES.inspect
    require '../dev/foo'
    puts $LOADED_FEATURES.inspect
  

If you execute ruby main.rb you will see following message.

  
    I am being loaded
    ["enumerator.so", "foo.rb"]
    I am being loaded
    ["enumerator.so", "foo.rb", "../dev/foo.rb"]
  

As you can see ruby while loading a file also stores the path that was provided. Now that we know that ruby looks at the path fix is easy. Here is fixed version.

  
    require File.expand_path('../foo', __FILE__)
    puts $LOADED_FEATURES.inspect
    require File.expand_path('../../lab/foo', __FILE__)
    puts $LOADED_FEATURES.inspect
  

If you execute ruby main.rb you will see following message.

  
    I am being loaded
    ["enumerator.so", "/Users/nsingh/dev/scratch/lab/foo.rb"]
    ["enumerator.so", "/Users/nsingh/dev/scratch/lab/foo.rb"]
  

As you can see foo.rb is being loaded only once and that is because $LOADED_FEATURES has the absolute path to foo.rb.

Summary

There is no need to have abosolute path in all the cases. However in a complex case where a number of different files are being required and some of the required files in turn require more files dynamically then it is best to have a policy of everyone requiring file using absolute path.

Recently in the Spree project, the tests started to fail with the message that factories are already loaded. This was because same factory was being loaded by two different gems using different relative paths. Once I converted the code to use absolute path this problem was fixed.

blog comments powered by Disqus