TLDR; Putting a constant inside `private` block is not enough to make it private. To achieve the desired effect you should use `Module#private_constant` method.

Using constants is idiomatic for Ruby. We use them to store something meaningful (a well-suited name is very important), connected with a class that holds it. Consequently, we make our code easier to reason about, avoid duplication and, very often, more performant. Let’s back to the `Language` class from the previous blog post holding all languages supported by an imaginary Ruby application:

require 'set' class Language def self.all SortedSet[ :en, :pl, # ...another locales supported by the app ] end end

It’s fairly common, that English is a default language for an application. To underline this fact, we can introduce a constant:

require 'set' class Language DEFAULT = :en def self.all SortedSet[ DEFAULT, :pl, # ...another locales supported by the app ].freeze end end

Language::DEFAULT => :en Language.constants => [:DEFAULT] Language::DEFAULT.object_id => 1287708 Language::DEFAULT.object_id => 1287708 # the same object is returned always

How to make a constant private?

Let’s assume that the class is one of the first ones inside a fresh Ruby application and others are not interested in the fact that the app has a default language. Language class encapsulates all the logic and that’s totally fine. To hide the constant we can limit its visibility:

require 'set' class Language def self.all SortedSet[ DEFAULT, :pl, # ...another locales supported by the app ].freeze end private DEFAULT = :en end

…and we are done for today, aren’t we?

Language::DEFAULT => :en

Ruby, you bastard 😉 Even though putting a constant inside private block is very intuitive due to the fact that we define private methods in that way, it does not do the same with constants.

Luckily, private_constant method was added to Module class in Ruby 1.9.3 to address this particular issue:

require 'set' class Language DEFAULT = :en private_constant :DEFAULT def self.all SortedSet[ DEFAULT, :pl, # ...another locales supported by the app ].freeze end end

Language.constants => [] Language::DEFAULT NameError (private constant Language::DEFAULT referenced)

Outside word is not aware of the private constant, checkmate 🙂

Summary

Our intuition is not always right and it is totally normal. Some time ago I was sure that putting a constant inside private block to make it hidden was enough when it wasn’t. You live you learn.

P.S. Do you find something else misleading in Ruby world? Share your thoughts in comments.