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.

Kategorien: Ruby, Software

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.

Footnotes:

1

Cf. only refine, using, __callee__, et al.

2

There are rumours about permitting emoji as class names in the future.

3

module lowercase causes just like a corresponding construction with a class a SyntaxError: „class/module name must be CONSTANT“.

4

See II. above.

5

Cf. only #send, which may also call private methods.

6

As of Ruby 2.5, there’s no protected_constant.