Rails Tip: Avoid mixing require and Rails autoloading
I've seen in a few Rails applications warnings about constants being redefined at some point. Problem was always the same: a file was autoloaded and required afterwards, and this results in the file being interpreted twice. If the class or module defines ordinary constants you may be lucky and see a warning, but if not you may not even be aware of it. Let me explain why this happens.
For example, given:
# lib/utils.rb module Utils X = 1 end
If we autoload the module and then require the file:
$ script/runner 'Utils; require "utils"'
a warning is issued:
warning: already initialized constant X
This is an artificial example, but in practice it may be the case for instance that some initializer autoloads Utils and later a model requires lib/utils.rb. Of course you don't need and shouldn't do that, but perhaps the model was written by someone not conversant in Rails or whatever.
OK, the warning is telling us lib/utils.rb is being interpreted twice. That happens both in development and production modes, but for different reasons.
In development mode Rails autoloading uses Kernel#load by default to be able to reinterpret code per request. So, the usage of the constant Utils triggers the interpretation of lib/utils.rb with load, and since require knows nothing about that file it happily interprets its content again.
In production mode Rails autoloading uses require, and that is supposed to run the file once, what's the matter?
When require loads something, it stores its path in the array $":
$ ruby -rdate -e 'puts $"' enumerator.so rational.rb date/format.rb date.rb
require checks that array to see whether a given file was already loaded before it attempts to go for it. If it was there's nothing to do and just returns (false). The point is that require does not detect whether two different paths point to the same file, and Rails autoloading passes absolute pathnames to autodiscovered source files:
$ script/runner -e production 'Utils; require "utils"; puts $"' ... /Users/fxn/tmp/test_require/lib/utils.rb utils.rb
Since they do not match, the file is again loaded twice.
The solution to this gotcha is as simple as removing the call to require. An idiomatic Rails application names its files after the classes or modules they define and delegate their loading to the dependencies mechanism. Generally, the only calls to require load external libraries.