Unmagic::Enum
Type-safe enums with attributes for Rails applications.
Features
- Type-safe enumeration values with string storage
- Custom attributes with defaults and aliases
- ActiveRecord integration with custom column type
- STI (Single Table Inheritance) support
- Query methods for checking enum values
- Duplicate key/value detection
- Works with symbols, integers, classes as keys
Installation
Add to your Gemfile:
gem 'unmagic-enum'
Usage
Basic Enum
class Status < Unmagic::Enum
ACTIVE = new("active")
PENDING = new("pending")
ARCHIVED = new("archived")
end
# Usage
status = Status::ACTIVE
status.active? # => true
status == "active" # => true
status.to_s # => "active"
Status["active"] # => Status::ACTIVE
Enum with Attributes
class Priority < Unmagic::Enum
attribute :label
attribute :color
attribute :level, default: 0
HIGH = new("high", label: "High Priority", color: "red", level: 3)
MEDIUM = new("medium", label: "Medium Priority", color: "yellow", level: 2)
LOW = new("low", label: "Low Priority", color: "green", level: 1)
end
priority = Priority::HIGH
priority.label # => "High Priority"
priority.color # => "red"
priority.level # => 3
ActiveRecord Integration
class Message < ApplicationRecord
class State < Unmagic::Enum
DRAFT = new("draft")
SENT = new("sent")
DELIVERED = new("delivered")
end
# Use the column type for proper serialization
attribute :state, State.column_type
# Create scopes
scope :delivered, -> { where(state: State::DELIVERED) }
end
# Usage
= Message.new(state: "sent")
.state # => Message::State::SENT
.state.sent? # => true
Multiple Values (JSON Array Columns)
For a json/jsonb column holding several values of one enum, pass array: true:
class Article < ApplicationRecord
class Topic < Unmagic::Enum
SCIENCE = new("science")
POLITICS = new("politics")
SPORT = new("sport")
end
attribute :topics, Topic.column_type(array: true)
end
article = Article.new(topics: ["science", "sport"])
article.topics # => [Article::Topic::SCIENCE, Article::Topic::SPORT]
Each element behaves like a scalar column_type: cast to an enum instance,
validated eagerly on assignment (an unknown element raises, unless built with
validate: true), and serialized to its database value. Blank elements are
dropped on cast — so the blank entry a check-box collection's auxiliary hidden
field submits never reaches the stored array — and stored values the enum no
longer recognises are dropped on read. The attribute always reads as an array,
never nil.
Key/Value Separation
Useful when database values differ from code identifiers:
class MessageType < Unmagic::Enum
USER = new("user") # key and value both "user"
ENTITY = new("entity", value: "bot") # key: "entity", value: "bot" (legacy DB)
SYSTEM = new("system", value: "s") # key: "system", value: "s" (short code)
end
MessageType::ENTITY.key # => "entity"
MessageType::ENTITY.value # => "bot"
MessageType["bot"] # => MessageType::ENTITY
STI Support
class User < ApplicationRecord
class Type < Unmagic::Enum
# Pass the actual class - no constantize needed!
CUSTOMER = new(Customer)
ADMIN = new(Admin)
MODERATOR = new(Moderator)
end
attribute :type, Type.column_type
def self.find_sti_class(type_name)
if enum_value = Type[type_name]
enum_value.key # Returns the class directly
else
super
end
end
end
Development
After checking out the repo, install dependencies and run the tests:
bundle install
bundle exec rake spec
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/unreasonable-magic/unmagic-enum.
License
Released under the MIT License.