[Proposal] Define constants with enum values

There are cases when you need to access the enum mapping directly. Currently, they are exposed as hash: Conversation.statuses[:active]. The problem occurs if you mistype status name, in that case you’ll receive nil. To my mind this problem is quite rare, but if it occurs and nobody has seen a typo, it can lead to redundant debug.
I am of the opinion that code architecture should not allow a developer to make such silly mistakes and notice it on first run. To avoid such problems rails could optionally define constants with enum’s values. Here is an example of possible enum definition:

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], define_constants: true

After that you can call constant like Conversation::ACTIVE which returns an appropriate value from enum(0 in this case). Constants generation would consider prefix and suffix options and define appropriate constant names.

Now if you use constants for direct mapping and make a typo, you’ll get NameError: uninitialized constant on the first launch and avoid redundant debug. This seems to me as an easy way to avoid typos when using enum direct mapping and also implementation shouldn’t take a lot of time.

If it would be useful in rails core I could implement this

Another kind of solution for this problem can be to have Conversation.statuses as a special subclass of a Hash, that raises when accessing the unknown key.

The use of constants for this purpose will be problematic since not every status would map to a valid constant name. For example, imagine if the enum was defined as:

class Conversation < ActiveRecord::Base
  enum status: [ "1_active", "2_archived" ]

The methods on Conversation objects are still defined and accessible via send:

convo = Conversation.new
convo.send("1_active!") # => true
convo.status # => "1_active"
convo.send("1_active?") # => true
convo.send("2_archived?") # => false

however, the related constants cannot be defined with those names.

Conversation.const_set("1_ACTIVE", 0) # => NameError: wrong constant name 1_ACTIVE

I agree with @fatkodima’s suggestion that if safety is the need, then making statuses a special object that raises for unknown keys is a better solution. It has the added benefit of not needing an extra option when defining enums and does not pollute the class with incidental constant definitions, while still satisfying the original need.

1 Like