Ruby Constants with Rails (and How to Avoid the “already initialized constant X” Warning)

If you have worked in Rails code (or just plain Ruby code) long enough, you have no doubt seen many messages similar to a.rb:10: warning: already initialized constant A::MAX
. What causes them? How can you debug them? What are the best practices for using constants so you avoid them in the future? Read on!
Behavior in Plain Ruby
Ruby has “open classes”, meaning they can be extended beyond the initial declaration. Methods may be redefined without any errors or warnings.
However, Ruby is not tolerant of reparsing the same class definition if it has constants declared in it. When those constants are re-initialized, they will generate the warning already initialized constant X
, as shown below:
$ cat a.rb
class A
MAX = 10
end
$ cat load_a.rb
load './a.rb'
load './a.rb'
$ ruby load_a.rb
a.rb:2: warning: already initialized constant A::MAX
a.rb:2: warning: previous definition of MAX was here
require
Idempotence
Unlike load
, require
is idempotent in Ruby. It returns false
and does nothing on subsequent invocations:
$ cat require_a.rb
result0 = require './a.rb'
result1 = require './a.rb'
puts [result0, result1]
$ ruby require_a.rb
true
false
require_relative
Is Just a Wrapper
Ruby’s require_relative
is just a wrapper around require
, so everything said about require
applies to require_relative
. The idempotence detector is shared across them in fact, so the above example works the same even if the second require './a.rb'
were written as require_relative 'a.rb'
.
Filename Aliasing
It is possible to trick the Ruby require
idempotence detector by using what appear to be different paths to the same file.
‘./’ and ‘../’
Ruby normalizes file paths by collapsing './'
and '../',
so using either of these will not trip it up.
Case Sensitivity in Filenames
Ruby assumes case-sensitive paths, so you will defeat the require
idempotence if you mix different path casing to the same file.
Linked Files or Paths
Ruby does not know about links in the file system, so you will defeat the require
idempotence if you mix different linked paths to the same file.
Avoid Constant Coupling
Constants are evaluated at class parse time, so you should be careful to avoid creating coupling, as in this example:
class AbcController
MAX_ITEMS = Widget::MAX + 1
CLIENT_ID = Secrets['Abc']['client_id']
...
end
The MAX_ITEMS
initialization above will force coupling to Widget
immediately right when Controller
is being parsed. To avoid this but still keep the code DRY, consider making a shared constant in a namespace that can be included by multiple consumers.
The CLIENT_ID
initialization above means that the Secrets
code has to run right then, at class parse time. This usually is not appropriate; if code has to run, it probably doesn't pass the test to be a constant! Better in that case to make it a class method, perhaps with memoization if there is no need to support the value changing over the lifetime of the process:
class AbcController
class << self
def client_id
@client_id ||= Secrets['Abc']['client_id']
end
end
...
end
Freeze Mutable Constants
It seems counter-intuitive…but constants in Ruby may be mutated! For example, consider a constant hash:
class
AbcController
DEFAULT_ARGS= { storage_engine: :AWS_S3
}
...
end
And then this code that passes that option to a method:
download_file(bucket, AbcController::DEFAULT_ARGS)
which eventually winds up mutating the argument:
def
download_file(bucket, options = {})
...
storage_engine = options.delete(:storage_engine)
end
When that code runs, AbcController::DEFAULT_ARGS
will be changed for the entire process!
To prevent this mistake, always freeze mutable constants:
class AbcController
DEFAULT_ARGS
= { storage_engine: :AWS_S3
}.freeze
...
end
Note: Assuming you have # frozen_string_literal: true
at the top of the file, strings literals are not mutable. So there’s no need to put .freeze
after string constants. In fact, the ever-vigilant Rubocop will cite you if you do.
Behavior in Rails
Things get more complicated in Rails.
Rails Auto-Loader
Rails has an auto-loader that knows how to load models and controllers and libs automatically when their name is mentioned. Also the auto-loader knows how to reload in development mode. It goes to great effort to avoid the “already initialized constant X” warning during this process.
You should never require
a file which is auto-loadable by Rails. Doing so will wind up loading the file twice… making this the most likely cause of “already initialized constant X” warning. By default Rails is configured to autoload from any sub-folders ofapp/
Your project may be configured to auto-load other folders likelib/
since that was in the default auto-load list for early versions of Rails.
Rails Non-Idempotence
Beware that some class macros are not idempotent in Rails, so breaking the above rule can cause bugs. One example is alias_method_chain
... but that is deprecated now, in favor of Module#prepend
. Another example is validations. If the file is parsed twice, the validations will be declared twice! For example:
pry> u = User.first;
pry> u.first_name = '';
pry> u.valid?
false
pry> u.errors.full_messages
["First name can't be blank"]
pry> require './app/models/user.rb' # <--- never do this!
true
pry> u.valid?
false
pry> u.errors.full_messages
["First name can't be blank", "First name can't be blank"] # Uh oh!
Technique for Tracking Down Repeated Loading
If you are seeing the already initialized constant X
warning, you can track down how that file got loaded by adding code like this to the file in question, to show the call stack when it's loaded:
# frozen_string_literal: true
puts "Start loading AbcController", caller, ""
class AbcController
...
end
puts "Finish loading AbcController"
Or, you may need to write to a log rather than using puts
. By having the caller
logged, you will be able to tell what code paths are responsible for loading the file each time.
Conclusion
Constants are a powerful tool in Ruby. They help keep code DRY and can make it easier to understand and debug, by separating static concerns that cannot change during process run-time from those that can. Hopefully this article will help you use Ruby constants more effectively.
We’re always looking for amazing people to join our team. Check out our job listings.
If this was interesting and helpful to you, please help share this with others by clapping 👏.