Private Classes and Constants in Ruby
Marvin Gülker · 31.03.2020
This article provides an overview of private classes and constants in the Ruby programming language.
A while ago I announced on the ruby-de mailing list that I intend to write an introduction to the Ruby programming language in German language. The project is making nice progress and while working on it, I find opportunities to work with the more obscure facets of Ruby. One of them is the topic of this blog post: private constants.
Note: This article is a translation of a German article I wrote earlier.
Introduction
Longstanding Rubyists, i.e. those who were using Ruby already in the times of Ruby 1.8 or earlier, often quickly state that there are no private constants in Ruby. This can even be proven prima facie:
class MyClass private MY_CONSTANT = 3 end puts MyClass::MY_CONSTANT #=> 3
Constants are not affected by the private
specifier. Up until Ruby
1.9.2 this piece of code was the end of story, which explains why the
subsequently described feature is only rarely mentioned, because in
the large shifts caused by Ruby 1.9 this rather minor change was too
easy to overlook.
In any case, the following rule still holds true: constants are public, unless specific measures are taken against that result. This is independent from the current method visibility.
private_constant
Starting with Ruby 1.9.3 Ruby supports the possibility to mark
constants as private. This does not make use of private
(for
compatibility reasons), but of a new method named
Module#private_constant. In the above example, it could have been used
like this:
class MyClass MY_CONSTANT = 3 private_constant :MY_CONSTANT end puts MyClass::MY_CONSTANT #=> NameError
Since that point real private constants in Ruby are possible,
independent from private
, which still only affects
methods. private_constant
also cannot be used without arguments to
change constant visibility for all subsequent constants, as can be
done with private
for methods. This simply has no effect:
class MyClass private_constant MY_CONSTANT = 3 end puts MyClass::MY_CONSTANT #=> 3
Accessing MY_CONSTANT
from the outside is still possible. If Ruby is
invoked in diagnostic mode (-w
), a corresponding warning is issued:
warning: private_constant with no argument is just ignored
private_constant is not a keyword, but a method (as is typical for
Ruby’s development11
Cf. only refine
, using
, __callee__
, et al.
). This is not a surprise
therefore. Considering that
private
itself only is a method as well, it is not necessaryly
consequent design, though. It should have been possible to choose a
different implementation.
Private Classes and Modules
In Ruby, all identifiers starting with a capital letter are
constants; starting with Ruby 2.6 this is going to hold true even for
capital letters outside the ASCII charset22
There are rumours about permitting emoji as class names in the future.
. Not only the
traditional, completely capital identifiers like MY_CONSTANT
in the
preceeding example are constants, but without exception all classes
and modules defined using the ordinary class
and module
keywords. Ruby’s grammar enforces the capital letter after one of
those keywords33
module lowercase
causes just like a corresponding
construction with a class a SyntaxError: „class/module name must be CONSTANT“.
.
private_constant
can be used with these “special” constants,
allowing to define private classes and even modules which may not be
accessed from the outside.
class MyClass class InnerClass end private_constant :InnerClass end MyClass::InnerClass.new #=> NameError
There are cases in which shielding certain “internal” classes from the
outside appears useful, and such cases may now be resolved by means of
private_constant
.
The access to the constant is checked if the :: operator is used. If a module (namespace) in a longer chain is marked as a private constant, all constants below it are — at least from outside that module — not accessible.
module Foo module Bar class Baz end end private_constant :Bar end Foo::Bar::Baz.new #=> NameError for Foo::Bar
Because private_constant
cannot be used like private
as a general
accessibility modifier44
See II. above.
it is necessary to repeat the class as
its argument again. The class and module definitions with class
and
module
sadly do not return the class’ resp. module’s name as it is
the case for def
with regard to methods. Therefore, the following is
not possible:
class MyClass private_constant class InnerClass end end
It is however possible to take advantage of Module#name in order to
achieve this behaviour with an additional line, because class
and
module
return their last expression’s value.
class MyClass private_constant class InnerClass name.split("::").last end end MyClass::InnerClass.new #=> NameError
This construction hurts the code’s readability. If at all, a meta method should be defined for it. To keep it universal, it should be extracted into a mixin, which can then be used with #extend where needed:
module InnerClassName def inner_name name.split("::").last end end class MyClass private_constant class InnerClass extend InnerClassName inner_name end end MyClass::InnerClass.new #=> NameError
Still, the effort required just for avoiding a single repetition
of the class name seems disproportional. Additionally, constructions like
private_constant class InerClass
do not look like idiomatic Ruby at
all, irrespective of how their validity is achieved. It is not
possible to create a similar method for regular constants (those that
are not classes or modules), which indicates that in the name of
symmetry between all kinds of constants such techniques should be
avoided.
Overruling the privacy
It is not untypical for the Ruby programming language that
restrictions can be circumvented with some effort55
Cf. only #send, which may also call private methods.
. Constants
marked with private_constant
are no different. Because constants are
resolved lexically and Ruby’s classes and modules are open, reopening
the class or module is an easy way to gain access to the presumly
private constant.
class MyClass MY_CONSTANT = 3 private_constant :MY_CONSTANT end class MyClass puts MY_CONSTANT #=> 3 end
Another possibility is to use the reflection method Module#const_get,
which is often used in metaprogramming. This method ignores the
privacy marker. Addition of another reflection method
public_const_get
was proposed, but not yet implemented.
class MyClass MY_CONSTANT = 3 private_constant :MY_CONSTANT end puts MyClass.const_get(:MY_CONSTANT) #=> 3
There’s also a counterpiece to private_constant
that removes the
privacy marker again: Module#public_constant66
As of Ruby 2.5, there’s no protected_constant
.
.
class MyClass MY_CONSTANT = 3 private_constant :MY_CONSTANT end class MyClass public_constant :MY_CONSTANT end puts MyClass::MY_CONSTANT #=> 3
Conclusion
With Ruby 1.9.3 it became possible to mark constants as private in
Ruby by means of calling private_constant
. The feature can be used
for better encapsulation of internal classes, modules, and other
constants. The repetition of the constant’s name has to be beared
with, and cannot be avoided for “regular” constants anyway. As it
often is the case with Ruby, one should however take into account that
the protection implemented by private_constant
is not absolute and
cannot only be overruled by means of metaprogramming, but simply by
reopening the respective class or module.