Enumerations and Ruby

Enumerations are an elegant way to make your code more readable. Unfortunately, Ruby doesn’t have a built-in enum type. However, if you only want to use an enumeration like a set of constants logically grouped, it’s super easy to do. You only have to create a class and define constants in it :

class Color
  BLUE=1
  RED=2
  GREEN=3
  YELLOW=4
  ORANGE=5
  PURPLE=6
end
paint_the_car(Color::YELLOW)

If you need a more elaborate enum, that is, if you want to iterate through every items like if it was a collection, you’ll have some more work to do (but not that much) :

class Color
    def Color.add_item(key,value)
        @hash ||= {}
        @hash[key]=value
    end
    def Color.const_missing(key)
        @hash[key]
    end
    def Color.each
        @hash.each {|key,value| yield(key,value)}
    end
    Color.add_item :BLUE, 1
    Color.add_item :RED, 2
    Color.add_item :YELLOW, 3
end
#That's it! We can now use our enum :
my_color = Color::RED if some_condition
#And we can loop
Color.each do |key,value|
  do_something_with_value(value)
end

Basically, we just used a hash in our class to make our enum behave like a collection.

  1. The add_item method is used to fill the hash and is called inside the class because we don’t want to give that responsability to the user of our enum. You’ll note that the calls to add_item are directly in the body of the class instead of being in the initialized method. That is because initialized is only called when you create an instance of a class (x = MyClass.new) and we don’t need instances of class Color.
  2. the const_missing method is used for convenience. We want to use Color::BLUE, not Color.the_internal_hash[:BLUE]. const_missing is called automatically when you try to access a constant that doesn’t exists. We then use that hook to return the hash element corresponding to the name supplied. We take for grant that this “missing constant” is in fact the desired key for our hash.
  3. We defined an iterator “each” to iterate through the collection. We could also have created an attribute reader that returns the hash and let the user of our class iterate from there. (like that : Color.items.each do …)
  4. We don’t create instances of class Color because we don’t need to. Everything happens at the class level, or if you prefer, in the object called Color. I could have enforced that behavior by preventing (if such a thing exists in ruby) users to create instances of that class, but I don’t think it was essential.
  5. In a recent article, I said that it wasn’t useful to use an instance variable (@) inside a class method. Well, this example demonstrates that it isn’t strictly true. If your class is also the only instance you need, variables that only live in this instance are useful.