Low commit activity in last 3 years
Define type-safe enums in Ruby with automatic ordinals, custom values, lookup methods, and Ruby 3.x pattern matching support. A cleaner alternative to ad-hoc constants and symbol sets.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-enum

Tests Gem Version Last updated

Type-safe enumerations with ordinals, custom values, and pattern matching

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-enum"

Or install directly:

gem install philiprehberger-enum

Usage

require "philiprehberger/enum"

class Status < Philiprehberger::Enum
  member :draft
  member :published
  member :archived
end

Status::DRAFT.name       # => :draft
Status::DRAFT.ordinal    # => 0
Status.members           # => [DRAFT, PUBLISHED, ARCHIVED]

Custom Values

class HttpCode < Philiprehberger::Enum
  member :ok, value: 200
  member :not_found, value: 404
  member :server_error, value: 500
end

HttpCode::OK.value          # => 200
HttpCode.from_value(404)    # => HttpCode::NOT_FOUND

Lookup Methods

Status.from_name(:draft)    # => Status::DRAFT
Status.from_string('draft') # => Status::DRAFT
Status.valid?(:draft)       # => true
Status.valid?(:unknown)     # => false

Enumerable

Enum classes include Enumerable, so you can iterate, map, select, etc.:

Status.each { |member| puts member.name }
Status.map(&:name)                # => [:draft, :published, :archived]
Status.select { |m| m.ordinal > 0 } # => [PUBLISHED, ARCHIVED]
Status.to_a                       # => [DRAFT, PUBLISHED, ARCHIVED]
Status.min                        # => DRAFT

Collection Methods

Status.to_h              # => { draft: nil, published: nil, archived: nil }
HttpCode.to_h            # => { ok: 200, not_found: 404, server_error: 500 }
HttpCode.members_by_value # => { 200 => OK, 404 => NOT_FOUND, 500 => SERVER_ERROR }
Status.size              # => 3
Status.count             # => 3

Strict Lookup

fetch and fetch_by_value raise an error instead of returning nil:

Status.fetch(:draft)          # => Status::DRAFT
Status.fetch(:unknown)        # raises Philiprehberger::Enum::Error
HttpCode.fetch_by_value(200)  # => HttpCode::OK
HttpCode.fetch_by_value(999)  # raises Philiprehberger::Enum::Error

Names, Values, First & Last

HttpCode.names   # => [:ok, :not_found, :server_error]
HttpCode.values  # => [200, 404, 500]
Status.first     # => Status::DRAFT
Status.last      # => Status::ARCHIVED

Slice

slice returns members matching the given names, silently skipping any that are unknown:

Status.slice(:draft, :archived)          # => [Status::DRAFT, Status::ARCHIVED]
Status.slice(:draft, :nonexistent)       # => [Status::DRAFT]
Status.slice(:archived, :draft)          # => [Status::ARCHIVED, Status::DRAFT]

Sample

sample returns a random member. Pass an integer to get an array:

Status.sample       # => Status::DRAFT  (random)
Status.sample(2)    # => [Status::PUBLISHED, Status::ARCHIVED]  (random)

Case-Insensitive Lookup

from_name tries an exact match first, then falls back to case-insensitive:

Status.from_name(:draft)    # => Status::DRAFT (exact match)
Status.from_name("DRAFT")   # => Status::DRAFT (case-insensitive)
Status.from_name("Draft")   # => Status::DRAFT (case-insensitive)

Comparison

Members are comparable by ordinal:

Status::DRAFT < Status::PUBLISHED   # => true
Status.members.sort                  # sorted by declaration order

Pattern Matching

case Status::DRAFT
in { name: :draft }
  'is draft'
in { name: :published }
  'is published'
end

Serialization

Status::DRAFT.to_s     # => "draft"
Status::DRAFT.to_json  # => '{"name":"draft","ordinal":0,"value":null}'

API

Philiprehberger::Enum (base class)

Method Description
.member(name, value: nil) Define a new enum member with optional custom value
.members Return all members in declaration order
.each Yield each member (includes Enumerable)
.to_h Return { name_symbol => value } hash
.members_by_value Return { value => member } reverse lookup hash
.size / .count Return the number of defined members
.names Return a frozen array of member name symbols
.values Return a frozen array of member values
.first / .last Return the first or last declared member
.fetch(name) Strict lookup by name; raises Error if not found
.fetch_by_value(val) Strict lookup by value; raises Error if not found
.from_name(name) Look up by name (case-insensitive fallback)
.from_string(string) Look up a member by string name
.from_value(val) Look up a member by custom value
.from_ordinal(ord) Look up a member by ordinal position (returns nil if out of range)
.fetch_by_ordinal(ord) Same as from_ordinal but raises Error if not found
.slice(*names) Return members matching the given symbol names, skipping unknowns
.sample(n = nil) Return a random member, or array of n random members
.valid?(name) Check if a name is a valid member
#name Return the member name as a symbol
#ordinal Return the ordinal position
#value Return the custom value, or nil
#to_s Return the member name as a string
#to_json Serialize to JSON with name, ordinal, and value
#deconstruct_keys(keys) Pattern matching support
#<=>(other) Compare by ordinal

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT