tags:

views:

296

answers:

2

First of all, this is not a duplicate of Enums in Ruby :)

The accepted answer of that question suggests this as a good way to represent enums in Ruby:

class Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

In Java it is possible to attach multiple values and methods to an enum value. I want to achive the same or something similar in Ruby.

What would be the most Ruby-like way to represent this Java enum:

public enum Enum

    VALUE_1("Value 1"),
    VALUE_2("Value 2"),
    VALUE_3("Value 3");

    Enum(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }
    private String value;
}  

EDIT:

I also want to keep the implicit features of Java enums:

  • ... retrieve the ordinal value
  • ... call methods on the enum values (or something equivalent)

Examples:

Enum.VALUE_1.getValue(); // "Value 1"
Enum.VALUE_2.name();     // "VALUE_2"
Enum.VALUE_3.ordinal();  // 2
+6  A: 
class MyEnum
  attr_accessor :value
  def initialize(value)
    @value = value
  end

  VALUE1 = new("Value 1")
  VALUE2 = new("Value 2")

  class << self
    private :new
  end
end

MyEnum::VALUE2 # Enum with value "Value 2"
MyEnum.new # Error

A more elaborate solution that allows you to define arbitrary "enum classes" and also gives you ordinal():

def enum(*values, &class_body)
  Class.new( Class.new(&class_body) ) do
    attr_reader :ordinal

    def initialize(ordinal, *args, &blk)
      super(*args, &blk)
      @ordinal = ordinal
    end

    values.each_with_index do |(name, *parameters), i|
      const_set(name, new(i, *parameters))
    end

    class <<self
      private :new
    end
  end
end

# Usage:
MyEnum = enum([:VALUE1, "Value 1"], [:VALUE2, "Value 2"]) do
  attr_reader :str
  def initialize(str)
    @str = str
  end
end

MyEnum::VALUE1.str #=> "Value 1"
MyEnum::VALUE2.ordinal #=> 1
sepp2k
I think it's proper to remove attr_accessor :value, right?
khelll
Depending on the behaviour one wants, it might make sense to replace it with `attr_reader`. However I don't see why one would want to remove it completely because I expect one would at least want to be able to read the value, otherwise there'd be no point in having a value in the first place.
sepp2k
Absolutely true, at least he should be able to do: MyEnum::VALUE2.value , or we can override to_s to return the value, that would be more concise ;)
khelll
In that example, how would I retrieve the ordinal value (In Java: VALUE1.ordinal())
DR
You wouldn't. For that you'd either have to save the number as a second value, or code up a solution that keeps an index automatically.
sepp2k
Yes, but that was the point of my question :)
DR
Updated my question to be more precise.
DR
Thank you for the quick response!!
DR
+3  A: 

You can always create a system that's like the Java version:

module Foo
  class Value
    attr_reader :value

    def initialize(value)
      # Save a frozen, immutable copy
      @value = value.dup.freeze
    end

    # Patch in methods to make it appear more friendly and string-like
    alias_method :to_s, :value
    alias_method :inspect, :value
  end

  # Define constants
  BAR = Value.new('bar')
  BAZ = Value.new('baz')
  BIZ = Value.new('biz')
end

puts Foo::BAR
# => bar
tadman
+1 for aliasing to_s
khelll