Enum-like behavior for Ruby, heavily inspired by this, and improved upon another blog post.
Enums can be defined and accessed either as constants, or class methods, which is a matter of preference.
Define enums, and reference them as constants.
classOrderStateincludeRuby::Enumdefine:CREATED,'created'define:PAID,'paid'endOrderState::CREATED# 'created'OrderState::PAID# 'paid'OrderState::UNKNOWN# raises Ruby::Enum::Errors::UninitializedConstantErrorOrderState.keys# [ :CREATED, :PAID ]OrderState.values# [ 'created', 'paid' ]OrderState.to_h# { :CREATED => 'created', :PAID => 'paid' }Define enums, and reference them as class methods.
classOrderStateincludeRuby::Enumdefine:created,'created'define:paid,'paid'endOrderState.created# 'created'OrderState.paid# 'paid'OrderState.undefined# NoMethodError is raisedOrderState.keys# [ :created, :paid ]OrderState.values# ['created', 'paid']OrderState.to_h# { :created => 'created', :paid => 'paid' }The value is optional. If unspecified, the value will default to the key.
classOrderStateincludeRuby::Enumdefine:UNSPECIFIEDdefine:unspecifiedendOrderState::UNSPECIFIED# :UNSPECIFIEDOrderState.unspecified# :unspecifiedEnums support all Enumerable methods.
OrderState.eachdo |key,enum|
# key and enum.key are :CREATED, :PAID# enum.value is 'created', 'paid'endOrderState.each_keydo |key|
# :CREATED, :PAIDendOrderState.each_valuedo |value|
# 'created', 'paid'endOrderState.mapdo |key,enum|
# key and enum.key are :CREATED, :PAID# enum.value is 'created', 'paid'[enum.value,key]end# => [ ['created', :CREATED], ['paid', :PAID] ]OrderState.reduce([])do |arr,(key,enum)|
# key and enum.key are :CREATED, :PAID# enum.value is 'created', 'paid'arr << [enum.value,key]end# => [ ['created', :CREATED], ['paid', :PAID] ]OrderState.sort_bydo |key,enum|
# key and enum.key are :CREATED, :PAID# enum.value is 'created', 'paid'enum.value.lengthend# => [[:PAID, #<OrderState:0x0 @key=:PAID, @value="paid">], [:CREATED, #<OrderState:0x1 @key=:CREATED, @value="created">]]Several hash-like methods are supported.
OrderState.keys# => [:CREATED, :PAID]OrderState.values# => ['created', 'paid']OrderState.key?(:CREATED)# => trueOrderState.value(:CREATED)# => 'created'OrderState.key?(:FAILED)# => falseOrderState.value(:FAILED)# => nilOrderState.value?('paid')# => trueOrderState.key('paid')# => :PAIDOrderState.value?('failed')# => falseOrderState.key('failed')# => nilDefining duplicate enums raises Ruby::Enum::Errors::DuplicateKeyError.
classOrderStateincludeRuby::Enumdefine:CREATED,'created'define:CREATED,'recreated'# raises DuplicateKeyErrorendDefining a duplicate value raises Ruby::Enum::Errors::DuplicateValueError.
classOrderStateincludeRuby::Enumdefine:CREATED,'created'define:RECREATED,'created'# raises DuplicateValueErrorendThe DuplicateValueError exception is raised to be consistent with the unique key constraint. Since keys are unique, there needs to be a way to map values to keys using OrderState.value('created').
When inheriting from a Ruby::Enum class, all defined enums in the parent class will be accessible in subclasses as well. Subclasses can also provide extra enums, as usual.
classOrderStateincludeRuby::Enumdefine:CREATED,'CREATED'define:PAID,'PAID'endclassShippedOrderState < OrderStatedefine:PREPARED,'PREPARED'define:SHIPPED,'SHIPPED'endShippedOrderState::CREATED# 'CREATED'ShippedOrderState::PAID# 'PAID'ShippedOrderState::PREPARED# 'PREPARED'ShippedOrderState::SHIPPED# 'SHIPPED'The values class method will enumerate the values from all base classes.
OrderState.values# ['CREATED', 'PAID']ShippedOrderState.values# ['CREATED', 'PAID', 'PREPARED', SHIPPED']All other enumerating and hashing methods (keys, key?, value?, key, value, to_h, parse and each) also consider enums defined anywhere in the class hierarchy.
ShippedOrderState.keys# [:CREATED, :PAID, :PREPARED, :SHIPPED]ShippedOrderState.key?(:CREATED)# trueShippedOrderState.value(:CREATED)# 'CREATED'A subclass may redefine a key or value already used by a parent class without raising DuplicateKeyError or DuplicateValueError; its own definition takes precedence.
classShippedOrderState < OrderStatedefine:CREATED,'RECREATED'# does not raise, overrides the parent class' definitionendShippedOrderState::CREATED# 'RECREATED'ShippedOrderState.value(:CREATED)# 'RECREATED'OrderState.value(:CREATED)# 'CREATED', unaffectedIf you want to make sure that you cover all cases in a case stament, you can use the exhaustive case matcher: Ruby::Enum::Case. It will raise an error if a case/enum value is not handled, or if a value is specified that's not part of the enum. This is inspired by the Rust Pattern Syntax. If multiple cases match, all matches are being executed. The return value is the value from the matched case, or an array of return values if multiple cases matched.
NOTE: This will add checks at runtime which might lead to worse performance. See benchmarks.
NOTE:
:elseis a reserved keyword if you want to useRuby::Enum::Case.
classColor < OrderStateincludeRuby::EnumincludeRuby::Enum::Casedefine:RED,:reddefine:GREEN,:greendefine:BLUE,:bluedefine:YELLOW,:yellowendcolor=Color::REDColor.Case(color,{[Color::GREEN,Color::BLUE]=>->{"order is green or blue"},Color::YELLOW=>->{"order is yellow"},Color::RED=>->{"order is red"},})It also supports default/else:
color=Color::REDColor.Case(color,{[Color::GREEN,Color::BLUE]=>->{"order is green or blue"},else: ->{"order is yellow or red"},})This gem has an optional dependency to i18n. If it's available, the error messages will have a nice description and can be translated. If it's not available, the errors will only contain the message keys.
# Add this to your Gemfile if you want to have a nice error description instead of just a message key.gem"i18n"Benchmark scripts are defined in the benchmarks folder and can be run with Rake:
rake benchmark:basicrake benchmark:caserake benchmark:inheritanceConstant access (e.g. Colors::RED) has no measurable overhead versus a plain Ruby constant, since it's just a constant lookup either way. Basic operations backed by a hash lookup - value, key, key?, value?, keys, values - carry some overhead (roughly 3-5x) compared to using a plain Hash directly, due to the extra method dispatch and object wrapping Ruby::Enum does internally. Run rake benchmark:basic to measure this on your own machine.
This overhead is constant regardless of how deep a subclass hierarchy is - keys, key?, value?, key, value, to_h, parse and each merge and memoize enums inherited from superclasses, so a subclass' lookups are effectively as fast as the base class' (see rake benchmark:inheritance).
The one notable exception is Ruby::Enum::Case, whose exhaustive case-like matcher is significantly slower (on the order of 50-100x, see rake benchmark:case) than a native Ruby case/when statement, since it builds and evaluates lambdas on every call rather than being optimized by the Ruby VM. Prefer a native case statement in hot code paths and reserve Ruby::Enum::Case for cases where its exhaustiveness check is worth the overhead.
For most applications this overhead is negligible in absolute terms (low single-digit microseconds per call), but it's worth being aware of in very hot code paths.
You're encouraged to contribute to ruby-enum. See CONTRIBUTING for details.
Copyright (c) 2013-2026, Daniel Doubrovkine and Contributors.
This project is licensed under the MIT License.
- typesafe_enum: Typesafe enums, inspired by Java.
- renum: A readable, but terse enum.