How to implement Enums in Ruby?
What's the best way to implement the enum idiom in Ruby? I'm looking for something which I can use (almost) like the Java/C# enums.
Solution 1:
Two ways. Symbols (:foo
notation) or constants (FOO
notation).
Symbols are appropriate when you want to enhance readability without littering code with literal strings.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Constants are appropriate when you have an underlying value that is important. Just declare a module to hold your constants and then declare the constants within that.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
Added 2021-01-17
If you are passing the enum value around (for example, storing it in a database) and you need to be able to translate the value back into the symbol, there's a mashup of both approaches
COMMODITY_TYPE = {
currency: 1,
investment: 2,
}
def commodity_type_string(value)
COMMODITY_TYPE.key(value)
end
COMMODITY_TYPE[:currency]
This approach inspired by andrew-grimm's answer https://stackoverflow.com/a/5332950/13468
I'd also recommend reading through the rest of the answers here since there are a lot of ways to solve this and it really boils down to what it is about the other language's enum that you care about
Solution 2:
I'm surprised that no one has offered something like the following (harvested from the RAPI gem):
class Enum
private
def self.enum_attr(name, num)
name = name.to_s
define_method(name + '?') do
@attrs & num != 0
end
define_method(name + '=') do |set|
if set
@attrs |= num
else
@attrs &= ~num
end
end
end
public
def initialize(attrs = 0)
@attrs = attrs
end
def to_i
@attrs
end
end
Which can be used like so:
class FileAttributes < Enum
enum_attr :readonly, 0x0001
enum_attr :hidden, 0x0002
enum_attr :system, 0x0004
enum_attr :directory, 0x0010
enum_attr :archive, 0x0020
enum_attr :in_rom, 0x0040
enum_attr :normal, 0x0080
enum_attr :temporary, 0x0100
enum_attr :sparse, 0x0200
enum_attr :reparse_point, 0x0400
enum_attr :compressed, 0x0800
enum_attr :rom_module, 0x2000
end
Example:
>> example = FileAttributes.new(3)
=> #<FileAttributes:0x629d90 @attrs=3>
>> example.readonly?
=> true
>> example.hidden?
=> true
>> example.system?
=> false
>> example.system = true
=> true
>> example.system?
=> true
>> example.to_i
=> 7
This plays well in database scenarios, or when dealing with C style constants/enums (as is the case when using FFI, which RAPI makes extensive use of).
Also, you don't have to worry about typos causing silent failures, as you would with using a hash-type solution.
Solution 3:
The most idiomatic way to do this is to use symbols. For example, instead of:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
...you can just use symbols:
# You don't actually need to declare these, of course--this is
# just to show you what symbols look like.
:foo
:bar
:baz
my_func(:foo)
This is a bit more open-ended than enums, but it fits well with the Ruby spirit.
Symbols also perform very well. Comparing two symbols for equality, for example, is much faster than comparing two strings.
Solution 4:
I use the following approach:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
I like it for the following advantages:
- It groups values visually as one whole
- It does some compilation-time checking (in contrast with just using symbols)
- I can easily access the list of all possible values: just
MY_ENUM
- I can easily access distinct values:
MY_VALUE_1
- It can have values of any type, not just Symbol
Symbols may be better cause you don't have to write the name of outer class, if you are using it in another class (MyClass::MY_VALUE_1
)
Solution 5:
If you are using Rails 4.2 or greater you can use Rails enums.
Rails now has enums by default without the need for including any gems.
This is very similar (and more with features) to Java, C++ enums.
Quoted from http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html :
class Conversation < ActiveRecord::Base
enum status: [ :active, :archived ]
end
# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status # => "active"
# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status # => "archived"
# conversation.update! status: 1
conversation.status = "archived"
# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status # => nil