How to implement Enums in Ruby?

RubyEnums

Ruby Problem Overview


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.

Ruby Solutions


Solution 1 - Ruby

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 - Ruby

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 - Ruby

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 - Ruby

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:

  1. It groups values visually as one whole
  2. It does some compilation-time checking (in contrast with just using symbols)
  3. I can easily access the list of all possible values: just MY_ENUM
  4. I can easily access distinct values: MY_VALUE_1
  5. 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 - Ruby

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

Solution 6 - Ruby

This is my approach to enums in Ruby. I was going for short and sweet, not necessarily the the most C-like. Any thoughts?

module Kernel
  def enum(values)
    Module.new do |mod|
      values.each_with_index{ |v,i| mod.const_set(v.to_s.capitalize, 2**i) }

      def mod.inspect
        "#{self.name} {#{self.constants.join(', ')}}"
      end
    end
  end
end

States = enum %w(Draft Published Trashed)
=> States {Draft, Published, Trashed} 

States::Draft
=> 1

States::Published
=> 2

States::Trashed
=> 4

States::Draft | States::Trashed
=> 5

Solution 7 - Ruby

I know it's been a long time since the guy posted this question, but I had the same question and this post didn't give me the answer. I wanted an easy way to see what the number represents, easy comparison, and most of all ActiveRecord support for lookup using the column representing the enum.

I didn't find anything, so I made an awesome implementation called yinum which allowed everything I was looking for. Made ton of specs, so I'm pretty sure it's safe.

Some example features:

COLORS = Enum.new(:COLORS, :red => 1, :green => 2, :blue => 3)
=> COLORS(:red => 1, :green => 2, :blue => 3)
COLORS.red == 1 && COLORS.red == :red
=> true

class Car < ActiveRecord::Base    
  attr_enum :color, :COLORS, :red => 1, :black => 2
end
car = Car.new
car.color = :red / "red" / 1 / "1"
car.color
=> Car::COLORS.red
car.color.black?
=> false
Car.red.to_sql
=> "SELECT `cars`.* FROM `cars` WHERE `cars`.`color` = 1"
Car.last.red?
=> true

Solution 8 - Ruby

Check out the ruby-enum gem, https://github.com/dblock/ruby-enum.

class Gender
  include Enum
  
  Gender.define :MALE, "male"
  Gender.define :FEMALE, "female"
end

Gender.all
Gender::MALE

Solution 9 - Ruby

Perhaps the best lightweight approach would be

module MyConstants
  ABC = Class.new
  DEF = Class.new
  GHI = Class.new
end

This way values have associated names, as in Java/C#:

MyConstants::ABC
=> MyConstants::ABC

To get all values, you can do

MyConstants.constants
=> [:ABC, :DEF, :GHI] 

If you want an enum's ordinal value, you can do

MyConstants.constants.index :GHI
=> 2

Solution 10 - Ruby

If you're worried about typos with symbols, make sure your code raises an exception when you access a value with a non-existent key. You can do this by using fetch rather than []:

my_value = my_hash.fetch(:key)

or by making the hash raise an exception by default if you supply a non-existent key:

my_hash = Hash.new do |hash, key|
  raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
end

If the hash already exists, you can add on exception-raising behaviour:

my_hash = Hash[[[1,2]]]
my_hash.default_proc = proc do |hash, key|
  raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"
end

Normally, you don't have to worry about typo safety with constants. If you misspell a constant name, it'll usually raise an exception.

Solution 11 - Ruby

Another solution is using OpenStruct. Its pretty straight forward and clean.

https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

Example:

# bar.rb
require 'ostruct' # not needed when using Rails

# by patching Array you have a simple way of creating a ENUM-style
class Array
   def to_enum(base=0)
	  OpenStruct.new(map.with_index(base).to_h)
   end
end

class Bar

	MY_ENUM = OpenStruct.new(ONE: 1, TWO: 2, THREE: 3)
	MY_ENUM2 = %w[ONE TWO THREE].to_enum

	def use_enum (value)
		case value
		when MY_ENUM.ONE
			puts "Hello, this is ENUM 1"
		when MY_ENUM.TWO
			puts "Hello, this is ENUM 2"
		when MY_ENUM.THREE
			puts "Hello, this is ENUM 3"
		else
			puts "#{value} not found in ENUM"
		end
	end

end

# usage
foo = Bar.new    
foo.use_enum 1
foo.use_enum 2
foo.use_enum 9


# put this code in a file 'bar.rb', start IRB and type: load 'bar.rb'

Solution 12 - Ruby

It all depends how you use Java or C# enums. How you use it will dictate the solution you'll choose in Ruby.

Try the native Set type, for instance:

>> enum = Set['a', 'b', 'c']
=> #<Set: {"a", "b", "c"}>
>> enum.member? "b"
=> true
>> enum.member? "d"
=> false
>> enum.add? "b"
=> nil
>> enum.add? "d"
=> #<Set: {"a", "b", "c", "d"}>

Solution 13 - Ruby

Someone went ahead and wrote a ruby gem called Renum. It claims to get the closest Java/C# like behavior. Personally I'm still learning Ruby, and I was a little shocked when I wanted to make a specific class contain a static enum, possibly a hash, that it wasn't exactly easily found via google.

Solution 14 - Ruby

Recently we released a gem that implements Enums in Ruby. In my post you will find the answers on your questions. Also I described there why our implementation is better than existing ones (actually there are many implementations of this feature in Ruby yet as gems).

Solution 15 - Ruby

Symbols is the ruby way. However, sometimes one need to talk to some C code or something or Java that expose some enum for various things.


#server_roles.rb
module EnumLike
   
  def EnumLike.server_role
    server_Symb=[ :SERVER_CLOUD, :SERVER_DESKTOP, :SERVER_WORKSTATION]
    server_Enum=Hash.new
    i=0
    server_Symb.each{ |e| server_Enum[e]=i; i +=1}
    return server_Symb,server_Enum
  end
 
end

This can then be used like this


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()
            
foreignvec[sEnum[:SERVER_WORKSTATION]]=8

This is can of course be made abstract and you can roll our own Enum class

Solution 16 - Ruby

I have implemented enums like that

module EnumType
  
  def self.find_by_id id
    if id.instance_of? String
      id = id.to_i
    end 
    values.each do |type|
      if id == type.id
        return type
      end
    end
    nil
  end

  def self.values
    [@ENUM_1, @ENUM_2] 
  end

  class Enum
    attr_reader :id, :label

    def initialize id, label
      @id = id
      @label = label
    end
  end

  @ENUM_1 = Enum.new(1, "first")
  @ENUM_2 = Enum.new(2, "second")

end

then its easy to do operations

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values

Solution 17 - Ruby

module Status
  BAD  = 13
  GOOD = 24

  def self.to_str(status)
    for sym in self.constants
      if self.const_get(sym) == status
        return sym.to_s
      end
    end
  end

end


mystatus = Status::GOOD

puts Status::to_str(mystatus)

Output:

GOOD

Solution 18 - Ruby

This seems a bit superfluous, but this is a methodology that I have used a few times, especially where I am integrating with xml or some such.

#model
class Profession
  def self.pro_enum
    {:BAKER => 0, 
     :MANAGER => 1, 
     :FIREMAN => 2, 
     :DEV => 3, 
     :VAL => ["BAKER", "MANAGER", "FIREMAN", "DEV"]
    }
  end
end

Profession.pro_enum[:DEV]      #=>3
Profession.pro_enum[:VAL][1]   #=>MANAGER

This gives me the rigor of a c# enum and it is tied to the model.

Solution 19 - Ruby

Most people use symbols (that's the :foo_bar syntax). They're sort of unique opaque values. Symbols don't belong to any enum-style type so they're not really a faithful representation of C's enum type but this is pretty much as good as it gets.

Solution 20 - Ruby

irb(main):016:0> num=[1,2,3,4]
irb(main):017:0> alph=['a','b','c','d']
irb(main):018:0> l_enum=alph.to_enum
irb(main):019:0> s_enum=num.to_enum
irb(main):020:0> loop do
irb(main):021:1* puts "#{s_enum.next} - #{l_enum.next}"
irb(main):022:1> end

Output:

1 - a
2 - b
3 - c
4 - d

Solution 21 - Ruby

Sometimes all I need is to be able to fetch enum's value and identify its name similar to java world.

module Enum
     def get_value(str)
       const_get(str)
     end
     def get_name(sym)
       sym.to_s.upcase
     end
 end

 class Fruits
   include Enum
   APPLE = "Delicious"
   MANGO = "Sweet"
 end

 Fruits.get_value('APPLE') #'Delicious'
 Fruits.get_value('MANGO') # 'Sweet'

 Fruits.get_name(:apple) # 'APPLE'
 Fruits.get_name(:mango) # 'MANGO'

This to me serves the purpose of enum and keeps it very extensible too. You can add more methods to the Enum class and viola get them for free in all the defined enums. for example. get_all_names and stuff like that.

Solution 22 - Ruby

Try the inum. https://github.com/alfa-jpn/inum

class Color < Inum::Base
  define :RED
  define :GREEN
  define :BLUE
end

Color::RED 
Color.parse('blue') # => Color::BLUE
Color.parse(2)      # => Color::GREEN

see more https://github.com/alfa-jpn/inum#usage

Solution 23 - Ruby

Another approach is to use a Ruby class with a hash containing names and values as described in the following RubyFleebie blog post. This allows you to convert easily between values and constants (especially if you add a class method to lookup the name for a given value).

Solution 24 - Ruby

I think the best way to implement enumeration like types is with symbols since the pretty much behave as integer (when it comes to performace, object_id is used to make comparisons ); you don't need to worry about indexing and they look really neat in your code xD

Solution 25 - Ruby

Another way to mimic an enum with consistent equality handling (shamelessly adopted from Dave Thomas). Allows open enums (much like symbols) and closed (predefined) enums.

class Enum
  def self.new(values = nil)
    enum = Class.new do
      unless values
        def self.const_missing(name)
          const_set(name, new(name))
        end
      end

      def initialize(name)
        @enum_name = name
      end

      def to_s
        "#{self.class}::#@enum_name"
      end
    end

    if values
      enum.instance_eval do
        values.each { |e| const_set(e, enum.new(e)) }
      end
    end

    enum
  end
end

Genre = Enum.new %w(Gothic Metal) # creates closed enum
Architecture = Enum.new           # creates open enum

Genre::Gothic == Genre::Gothic        # => true
Genre::Gothic != Architecture::Gothic # => true

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionauramoView Question on Stackoverflow
Solution 1 - RubymlibbyView Answer on Stackoverflow
Solution 2 - RubyCharlesView Answer on Stackoverflow
Solution 3 - RubyemkView Answer on Stackoverflow
Solution 4 - RubyAlexeyView Answer on Stackoverflow
Solution 5 - RubyVedant AgarwalaView Answer on Stackoverflow
Solution 6 - RubyjohnnypezView Answer on Stackoverflow
Solution 7 - RubyOded NivView Answer on Stackoverflow
Solution 8 - RubydB.View Answer on Stackoverflow
Solution 9 - RubyDaniel LubarovView Answer on Stackoverflow
Solution 10 - RubyAndrew GrimmView Answer on Stackoverflow
Solution 11 - RubyRogerView Answer on Stackoverflow
Solution 12 - RubymislavView Answer on Stackoverflow
Solution 13 - RubydlamblinView Answer on Stackoverflow
Solution 14 - Rubyka8725View Answer on Stackoverflow
Solution 15 - RubyJonkeView Answer on Stackoverflow
Solution 16 - RubyMasuschiView Answer on Stackoverflow
Solution 17 - RubyHosseinView Answer on Stackoverflow
Solution 18 - RubyjjkView Answer on Stackoverflow
Solution 19 - RubyJan KrügerView Answer on Stackoverflow
Solution 20 - RubyAnuView Answer on Stackoverflow
Solution 21 - Rubydark_srcView Answer on Stackoverflow
Solution 22 - RubyhorunView Answer on Stackoverflow
Solution 23 - RubyPhilippe MonnetView Answer on Stackoverflow
Solution 24 - RubygoreortoView Answer on Stackoverflow
Solution 25 - RubyDaniel DoubledayView Answer on Stackoverflow