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

Colin Kelley
Oct 11, 2018 · 5 min read

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

Invoca Engineering Blog

Invoca is a SaaS company helping marketers optimize for the most important step in the customer journey: the phone call.

Colin Kelley

Written by

Invoca Engineering Blog

Invoca is a SaaS company helping marketers optimize for the most important step in the customer journey: the phone call.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade