Fully Loaded
Assuming the file ‘dog.rb’ exists in the load path and defines a Dog
class,
what will be the output of this code?
Dog.new
p require 'dog.rb'
Will this raise a NameError
? Will require
return true
or false
?
Well, that depends…
Loading source files
Ruby offers several methods for loading source files, including
require
, require_relative
,
load
, and autoload
.
These methods are well-documented, and generally behave in predictable ways.
require
loads a file with the given name, and returns true
.
If the file has already been loaded,
the method will simply return false
.
p require 'dog.rb'
#=> true
p require 'dog.rb'
#=> false
load
works similarly, but it will reload the file and return true
each time it is called. This is useful in development, where you might
want to reload a file whenever it changes.
p load 'dog.rb'
#=> true
p load 'dog.rb'
#=> true
If a file has already been loaded with require
, load
will reload it.
p require 'dog.rb'
#=> true
p load 'dog.rb'
#=> true
This next example surprised me. If you load
a file and then require
it,
require
will reload the file and return true
. You wouldn’t normally do this,
but I did get into trouble once when I mixed require
with
Rails’ autoloading (more on autoloading below).
p load `dog.rb`
#=> true
p require `dog.rb`
#=> true
require
only returns false
when
the file already appears in the $LOADED_FEATURES
array.
While require
adds an entry to $LOADED_FEATURES
,
thereby preventing the second require
,
load
does not.
def dog_required?
$LOADED_FEATURES.include? File.join(Dir.pwd, 'dog.rb')
end
p dog_required?
#=> false
load 'dog.rb'
p dog_required?
#=> false
require 'dog.rb'
p dog_required?
#=> true
For those interested in the internals,
Ruby defines load
and require
in load.c.
These call the C functions rb_f_load
and rb_f_require
,
which in turn call rb_load_internal
and rb_require_internal
.
The function that pushes the entry into $LOADED_FEATURES
is called rb_provide_features
.
Notice that rb_require_internal
calls rb_provide_feature
,
but rb_load_internal
does not.
I played around with a branch that
adds a call to rb_provide_feature
inside rb_load_internal
,
but the tests for Kernel#load
make it
clear that is not the expected behavior.
Rails autoloading
Let’s get back to our first example. If we run this file without any other dependencies,
Dog.new
will raise an error, since we refer to the Dog
constant before loading
the file that defines it.
Dog.new
#=> NameError: uninitialized constant Dog
But what if this is part of a Rails application?
Rails autoloads constants based on a few naming conventions.
If ‘dog.rb’ is defined in one of the directories in the config.autoload_paths
array,
Rails will load the file automatically when you refer to the Dog
constant.
When you refer to an undefined constant, Ruby calls const_missing
.
Rails overrides const_missing
and
attempts to load the missing constant.
But will Rails load
your file, or require
it? Well that depends on the environment.
Specifically, it depends on the value of config.cache_classes
, which is normally
set to false
in development and true
in production. This translates
to load
ing files in development and require
ing them in production.
So what will be the output of this code?
Dog.new
p require 'dog.rb'
Usually it will raise a NameError
on line 1.
But in Rails it will print false
in production
(the file will already have been require
d by Rails’ autoloading system),
and true
in development
(the file will have been load
ed, but that does not populate $LOADED_FEATURES
)
But remember, this is Ruby; it is possible that ‘dog.rb’ will redefine require
.
class Dog
end
def require(filename)
filename
end
In this case the output of our example would be "dog.rb"
.
So our example will either raise a NameError
, print true
or false
, or do something else.