Constant Doubts
Tags: ruby, opinion
Published: 2023-03-23 12:58:47

---
title: Constant Doubts
published: true
description: Ruby's public constants often expose implementation details of the defining scope. This article explores a simple alternative option: singleton methods. Singleton methods everywhere.
tags: ruby, opinion
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bqnq6wefhb8fbgszbbyl.jpg
---

I always had mixed feelings towards Ruby constants. First, **they're not that constant** to begin with. You can reassign a constant at runtime freely and all you get is a warning:

```ruby
irb(main):001:0> FOO = :bar
irb(main):002:0> FOO = :lol
(irb):2: warning: already initialized constant FOO
(irb):1: warning: previous definition of FOO was here
```

Contrary to popular belief, a `#freeze` won't help, either. Yes, it makes the _object_ you're assigning immutable, but it doesn't prevent another constant _assignment_:

```ruby
irb(main):001:0> FOO = "bar".freeze
irb(main):002:0> FOO = "baz".freeze
(irb):2: warning: already initialized constant FOO
(irb):1: warning: previous definition of FOO was here
irb(main):003:0> # 🤷
```
<small>(It would be nice to be able to change warning into an error, but there's certainly no `RUBYOPT` flag that I know of.)</small>

But most importantly: unless they're defined in the top level object space, **I consider them implementation details**. If an instance of `A::Nested::Class` accesses  `Some::Other::CONST`, it crosses 5-6 boundaries (depending on where you start counting). That's not actually respecting that module's privacy and also a violation of the [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter).

## `private_constant` all the things!
By default, constants are public. Most software engineers are very eager to work with the `private` keyword to limit the public API of their instances, but it's rarer to see that same rigor applied to class or module-level constants.

Ruby has `private_constant` since basically forever (<small>MRI v1.9.3 to be precise</small>). It accepts one or more symbols referring to defined constants in its scope:

```ruby
module MyModule
  FOO = "bar".freeze
  LOL = :wat?

  private_constant :FOO, :LOL

  def self.foo
    FOO
  end
end
```
Any attempt to access `MyModule::FOO` from outside `MyModule` will raise a `NameError` ("private constant MyModule::FOO referenced").

Please note that `MyModule.foo` still works (and returns the frozen string `"bar"`) as it only accesses the private constant from within its defining scope.

## (Singleton) Methods Over Constants

I mentioned I consider constants implementation details. A named identifier for a magic value, maybe something configurable and set at load time. And sometimes, you want to expose that to other components in your applications.

As shown in the code snippet above, you can always create a singleton method / module function that wraps around a private constant.

In my opinion, methods are in all cases superior to `CONSTANTS`:
1. you can defer (lazy load) the assignment
2. you can memoize (`@class_var ||= ...`) what's being assigned
3. you can delegate the method call
4. it's easier to stub a method than a constant in your tests
5. it pairs well with making class/module-level value configurable on the application level, e.g. using a well-known `MyModule.configure(&block)` format or by using [dry-config](https://dry-rb.org/gems/dry-configurable/0.16/)
6. it feels much more OOP to send a message to an object than working with its constants (<small>also, I always feel that CONSTANTS YELL AT ME in the source code</small>)

Points 1..5 all give you a great forward compatible way to refactor how your magic value is used, all for the small price of making your constants private and adding a getter singleton method around it if you really need to expose it to the outside world right away.

## Enforce Explicit Constant Visibility

Unfortunately, there is no way to make all constant private by default, but RuboCop includes a [`RuboCop::Cop::Style::ConstantVisibility`](https://docs.rubocop.org/rubocop/1.46/cops_style.html#styleconstantvisibility) cop to at least make the constant scope explicit.

I like that it makes you stop and think about what you're doing.

## Named Classes Are Constants

Now, constants aren't only referred to by `UPPERCASE` identifiers: all class and module names are in fact constants, so you could argue that my criticism about accessing `Some::CONSTANT` directly must also extend to a form like `Nested::Class.new`.

And indeed it does, but that will be the topic of the next article. 😀

<small>Cover image credit: DALL·E 2.</small>