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.
class OrderState
include Ruby::Enum
define :CREATED, 'created'
define :PAID, 'paid'
endOrderState::CREATED # 'created'
OrderState::PAID # 'paid'
OrderState::UNKNOWN # raises Ruby::Enum::Errors::UninitializedConstantError
OrderState.keys # [ :CREATED, :PAID ]
OrderState.values # [ 'created', 'paid' ]
OrderState.to_h # { :CREATED => 'created', :PAID => 'paid' }Define enums, and reference them as class methods.
class OrderState
include Ruby::Enum
define :created, 'created'
define :paid, 'paid'
endOrderState.created # 'created'
OrderState.paid # 'paid'
OrderState.undefined # NoMethodError is raised
OrderState.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.
class OrderState
include Ruby::Enum
define :UNSPECIFIED
define :unspecified
endOrderState::UNSPECIFIED # :UNSPECIFIED
OrderState.unspecified # :unspecifiedEnums support all Enumerable methods.
OrderState.each do |key, enum|
# key and enum.key are :CREATED, :PAID
# enum.value is 'created', 'paid'
endOrderState.each_key do |key|
# :CREATED, :PAID
endOrderState.each_value do |value|
# 'created', 'paid'
endOrderState.map do |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_by do |key, enum|
# key and enum.key are :CREATED, :PAID
# enum.value is 'created', 'paid'
enum.value.length
end
# => [[: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)
# => true
OrderState.value(:CREATED)
# => 'created'
OrderState.key?(:FAILED)
# => false
OrderState.value(:FAILED)
# => nilOrderState.value?('paid')
# => true
OrderState.key('paid')
# => :PAID
OrderState.value?('failed')
# => false
OrderState.key('failed')
# => nilDefining duplicate enums raises Ruby::Enum::Errors::DuplicateKeyError.
class OrderState
include Ruby::Enum
define :CREATED, 'created'
define :CREATED, 'recreated' # raises DuplicateKeyError
endDefining a duplicate value raises Ruby::Enum::Errors::DuplicateValueError.
class OrderState
include Ruby::Enum
define :CREATED, 'created'
define :RECREATED, 'created' # raises DuplicateValueError
endThe 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 sub classes as well. Sub classes can also provide extra enums, as usual.
class OrderState
include Ruby::Enum
define :CREATED, 'CREATED'
define :PAID, 'PAID'
end
class ShippedOrderState < OrderState
define :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']If 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.
class Color < OrderState
include Ruby::Enum
include Ruby::Enum::Case
define :RED, :red
define :GREEN, :green
define :BLUE, :blue
define :YELLOW, :yellow
endcolor = Color::RED
Color.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::RED
Color.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 benchmarks:caseYou're encouraged to contribute to ruby-enum. See CONTRIBUTING for details.
Copyright (c) 2013-2021, 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.