Question
I want to implement an enum-style pattern in Ruby.
What is the best way to represent a fixed set of named values in Ruby so that I can use them similarly to Java or C# enums?
I am looking for an approach that is idiomatic in Ruby while still providing the clarity and safety of enums from statically typed languages.
Short Answer
By the end of this page, you will understand how Ruby handles enum-like values, why Ruby does not have Java-style enums built into the language in the same way, and which Ruby patterns are commonly used instead. You will learn when to use symbols, constants, custom classes, and framework features such as Rails enums, along with the trade-offs of each approach.
Concept
Ruby does not have a built-in enum feature that works exactly like Java or C# enums. Instead, Ruby developers usually model a fixed set of named values using simpler language features.
The core idea of an enum is this:
- You have a limited list of allowed values.
- Each value has a meaningful name.
- You want your code to be readable and less error-prone.
For example, an order status might only allow:
:pending:paid:shipped:cancelled
In Ruby, the most common enum-like choices are:
- Symbols for lightweight named values
- Constants for shared named values
- Arrays or hashes to define allowed options
- Custom classes/modules when you need richer behavior
- Rails
enumif you are using Active Record
Why this matters in real programming:
- It prevents invalid values from being scattered through your code.
- It makes conditions easier to read.
- It centralizes business rules.
- It reduces bugs caused by typos like
"pendng"instead of"pending".
Ruby favors simple, flexible patterns over strict language-level enum syntax. That means the “best” enum approach depends on what you need:
- If you only need named values, use symbols.
- If you need validation, keep a list of allowed values.
- If you need methods on each value, use a class-based approach.
- If you are in Rails models, use
enum.
So the Ruby way is usually not to imitate Java exactly, but to choose the lightest pattern that clearly expresses a fixed set of values.
Mental Model
Think of an enum as a menu with a fixed list of choices.
In Java or C#, the language gives you a special printed menu automatically. In Ruby, you usually create the menu yourself.
For example, imagine a coffee shop where drink sizes can only be:
- small
- medium
- large
You could represent that menu in Ruby as:
- symbols:
:small,:medium,:large - constants:
SMALL,MEDIUM,LARGE - a class if each size has extra behavior, like pricing rules
The important part is not the syntax. The important part is that your program treats the list as fixed and known in advance.
So an enum in Ruby is less like a special language feature and more like a well-organized set of allowed labels.
Syntax and Examples
1. Simple enum-like values with symbols
This is the most idiomatic Ruby approach for many cases.
STATUSES = [:pending, :paid, :shipped, :cancelled].freeze
status = :paid
if STATUSES.include?(status)
puts "Valid status"
else
puts "Invalid status"
end
Explanation:
:paidis a symbol.STATUSESstores the allowed values..freezeprevents accidental modification.include?checks whether the value is allowed.
2. Using constants for named values
module Status
PENDING = :pending
PAID = :paid
SHIPPED = :shipped
CANCELLED = :cancelled
ALL = [PENDING, PAID, SHIPPED, CANCELLED].freeze
end
status = Status::PAID
puts Status::ALL.include?(status)
This gives you a namespaced way to access the values, similar to enum members.
3. A class-based enum style
Use this when you want each enum value to be an object.
class Status
attr_reader :name
def initialize(name)
@name = name
freeze
end
PENDING = new("pending")
PAID = new("paid")
SHIPPED = new("shipped")
CANCELLED = new("cancelled")
ALL = [PENDING, PAID, SHIPPED, CANCELLED].freeze
def to_s
name
end
end
status = Status::PAID
puts status
puts Status::ALL.include?(status)
This is closer to Java-style enums because each value is a distinct object.
4. Rails Active Record enum
If you are using Rails, there is built-in support for model enums.
class Order < ApplicationRecord
enum :status, { pending: 0, paid: 1, shipped: 2, cancelled: 3 }
end
This gives you helpful methods such as:
order.paid!
order.paid?
Order.shipped
This is the best choice when the enum belongs to a database-backed model in Rails.
Step by Step Execution
Consider this example:
module Status
PENDING = :pending
PAID = :paid
SHIPPED = :shipped
ALL = [PENDING, PAID, SHIPPED].freeze
end
status = :paid
if Status::ALL.include?(status)
puts "Accepted: #{status}"
else
puts "Rejected"
end
Step by step:
- Ruby defines a module named
Status. - Inside the module, three constants are created:
Status::PENDINGStatus::PAIDStatus::SHIPPED
ALLstores every valid status in a frozen array.- The variable
statusis assigned the symbol:paid. Status::ALL.include?(status)checks whether:paidis inside the list.- Since it is included, the condition is true.
- Ruby prints:
Accepted: paid
Now imagine this value instead:
status = :unknown
Then include? returns false, so the output becomes:
Rejected
This shows the main purpose of an enum-like pattern in Ruby: keeping values restricted to a known set.
Real World Use Cases
Enum-style patterns are common whenever a value must come from a small predefined set.
Common examples
- Order status:
:pending,:paid,:shipped - User roles:
:admin,:editor,:viewer - Payment methods:
:card,:bank_transfer,:cash - Job states:
:queued,:running,:failed,:completed - Environment names:
:development,:test,:production
In scripts
A command-line script might only accept certain modes:
MODES = [:fast, :safe, :debug].freeze
In APIs
A request parameter may only allow certain values:
SORT_OPTIONS = [:asc, :desc].freeze
In data processing
A file import tool may classify rows as:
:valid:invalid:duplicate
The enum-like structure helps ensure downstream logic handles only expected categories.
Real Codebase Usage
In real Ruby codebases, developers often use enum-like patterns in a few practical ways.
1. Constants plus an ALL list
This is a common pattern for validation and clarity.
module Role
ADMIN = :admin
EDITOR = :editor
VIEWER = :viewer
ALL = [ADMIN, EDITOR, VIEWER].freeze
end
Why it is useful:
- avoids magic values spread across files
- gives one source of truth
- works well in validations and forms
2. Guard clauses for validation
def set_status(status)
raise ArgumentError, "Invalid status" unless Status::ALL.include?(status)
@status = status
end
This is a common Ruby style: reject invalid input early.
3. Mapping enum values to labels or behavior
STATUS_LABELS = {
pending: "Pending payment",
paid: "Paid",
shipped: "Shipped"
}.freeze
This keeps display text separate from program logic.
4. Using enums in Rails models
class User < ApplicationRecord
enum :role, { viewer: 0, editor: 1, admin: 2 }
end
This is common when a model attribute has a fixed set of states.
5. Encapsulating behavior when the values need logic
If each enum value needs methods, teams often use objects rather than plain symbols.
class LogLevel
attr_reader :name, :priority
def initialize(name, priority)
@name = name
@priority = priority
freeze
end
INFO = new("info", 1)
WARN = new("warn", 2)
ERROR = new("error", 3)
end
This is more verbose, but useful when values are more than labels.
Common Mistakes
1. Using plain strings everywhere
Broken example:
status = "Paid"
if status == "paid"
puts "OK"
end
Problem:
- string comparisons are easy to break because of case or spelling differences
Better:
status = :paid
2. Not centralizing allowed values
Broken example:
if status == :pending
# ...
elsif status == :paid
# ...
elsif status == :shipped
# ...
end
Problem:
- values are repeated throughout the codebase
- adding a new value becomes harder
Better:
module Status
PENDING = :pending
PAID = :paid
SHIPPED = :shipped
end
3. Forgetting to freeze shared collections
Broken example:
STATUSES = [:pending, :paid, :shipped]
STATUSES << :cancelled
Problem:
- other code can accidentally change the enum list
Better:
STATUSES = [:pending, :paid, :shipped].freeze
4. Expecting Java-style enum behavior automatically
Some beginners expect features like:
- strict type safety
- built-in iteration over enum members
- methods automatically attached to each enum member
Ruby does not provide that by default. You must choose a pattern that gives the features you need.
5. Overengineering a simple problem
If all you need is a fixed list of names, a full class-based enum may be too much.
Too complex for simple cases:
class Color
RED = new("red")
BLUE = new("blue")
end
Often simpler is better:
COLORS = [:red, :blue].freeze
6. Not validating external input
Broken example:
def create_order(status)
@status = status
end
Problem:
- any value can enter your system
Better:
def create_order(status)
raise ArgumentError, "Invalid status" unless Status::ALL.include?(status)
@status = status
end
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Symbols in an array | Simple fixed values | Idiomatic, short, easy to use | No extra behavior by default |
| Constants in a module | Named access like Status::PAID | Clear namespacing, readable | Slightly more setup |
| Custom class objects | Values with methods/data | Closest to Java-style enums | More code, more complexity |
| Strings | External input or serialization | Easy to print/store | More typo-prone in internal logic |
Rails enum | Active Record models | Built-in query and helper methods | Rails-specific |
Symbols vs strings
| Feature | Symbols | Strings |
|---|---|---|
| Typical for internal enum values | Yes | Less ideal |
| Easy to mistype | Less visible but still possible | Yes |
| Best for user input/output | Usually no | Yes |
| Common Ruby style | Very common | Common at boundaries |
Ruby enum patterns vs Java/C# enums
| Feature | Ruby patterns | Java/C# enums |
|---|---|---|
| Built into language | Usually no | Yes |
| Strict type safety | Limited | Stronger |
| Flexibility | High | Moderate |
| Simplicity for small cases | High | Sometimes more formal |
| Object behavior per value | Possible with custom classes | Built in |
Cheat Sheet
Quick reference
Simple enum-like list
STATUSES = [:pending, :paid, :shipped].freeze
Namespaced constants
module Status
PENDING = :pending
PAID = :paid
SHIPPED = :shipped
ALL = [PENDING, PAID, SHIPPED].freeze
end
Validate a value
Status::ALL.include?(status)
Raise on invalid input
raise ArgumentError, "Invalid status" unless Status::ALL.include?(status)
Class-based enum style
class Status
PENDING = new("pending")
end
Use this only if values need behavior or data.
Rails enum
class Order < ApplicationRecord
enum :status, { pending: 0, paid: 1, shipped: 2 }
end
Rules of thumb
- Prefer symbols for internal enum-like values.
- Use constants for readability and reuse.
- Keep a single
ALLlist for validation. - Freeze shared arrays and hashes.
- Use strings at system boundaries if needed, then convert.
- Use a custom class only when you need more than labels.
Edge cases
- External JSON or HTTP params usually arrive as strings.
- Symbols and strings are different:
:paid == "paid" # false
- Avoid scattered magic values like
:pendingin many files. - In Rails, prefer model-level
enumwhen the value is stored in the database.
FAQ
Does Ruby have built-in enums like Java or C#?
Not in the same way. Ruby usually uses symbols, constants, arrays, hashes, or framework helpers such as Rails enum.
What is the most idiomatic enum-like approach in Ruby?
For simple cases, symbols are the most idiomatic choice, often combined with a frozen list of allowed values.
Should I use symbols or strings for enum values in Ruby?
Use symbols for internal logic in most cases. Use strings mainly when receiving or sending external data, such as JSON or HTTP parameters.
How can I validate enum values in Ruby?
Store all allowed values in a constant like ALL and use include? to check whether a value is valid.
How do I make Ruby enums look more like Java enums?
Use a module with constants or create a custom class where each enum value is a singleton-like object.
What is the best enum option in Rails?
If the enum is a model attribute stored in the database, use Active Record enum.
Can Ruby enum values have methods?
Yes, but you usually need a custom class or objects instead of plain symbols.
Why should I freeze enum arrays or hashes?
Freezing prevents accidental modification of the shared list of allowed values.
Mini Project
Description
Build a small order status helper for an e-commerce application. The project demonstrates how to represent a fixed set of statuses in idiomatic Ruby, validate inputs, and convert those statuses into user-friendly labels. This is a practical example of how enum-like values are used in real code without needing Java-style language support.
Goal
Create a Ruby module and class that safely manage a fixed list of order statuses and reject invalid values.
Requirements
- Define a fixed set of order statuses using Ruby constants and symbols.
- Store all allowed statuses in one frozen collection.
- Create an
Orderclass that validates the status during initialization. - Add a method that returns a human-readable label for each status.
- Show example usage with both valid and invalid statuses.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.