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 :

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) :

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.
  • http://www.benlog.org Ben

    It seems to me that symbols have made enumerations redundant. Why not just use :blue, :red and :yellow, period?

  • Badaud

    [code]We don’t create instances of class Color because we don’t need to.[/code]
    Actually, you have to, otherwise you’re just defining constants and not Enums.

    And
    Color.BLUE could be equal to Chess.KING

    (Enums are not just about constant values, they’re about type safety)

  • http://www.rubyfleebie.com Frank

    @Ben,

    In this regard, symbols could be seen as “top level” enums. However, the advantage of putting related constants inside a class is that it adds semantics to your application. The constants can appear logically grouped instead of floating in the air on their own. Of course, I’m not saying that these custom enums should replace symbols. I just find it useful to group things that are related together.

    @Badaud,

    As you can see in my exemple, I don’t create any instance of class Color. I only work with the object Color, which is an instance of class Class. Also, you’ll note that i don’t write Color.BLUE but Color::BLUE, which triggers the const_missing method. It would be equivalent of doing : Color.const_missing(:BLUE) but there won’t be any point to access it that way! Finally, I don’t quite understand your claim about type safety. This was a practical example illustrating how to create an enum-like class in ruby. If for some reason Color::BLUE and Chess::KING would be equal… then so what? Those are integers values after all. The purpose (in this example at least) was to use an enum as a set of constants logically grouped together. (Like if “Color” was a namespace)

  • http://rikkus.info Rik Hemsley

    If you want to iterate through every item ‘as if it was a collection’, why not use a collection?

    Colors = { :blue => 1, :red => 2, :yellow => 5 }

  • http://www.rubyfleebie.com Frank

    @Rik,

    With your code, you would have to create an instance of Hash class everytime you would want to use the collection. If “Colors” is a local variable, it would goes out of scope pretty quick. If it is an instance variable, you would still have one hash for every instances of the class that contains it. What is the other option? To make your hash global? hmm.. bad idea :)

    If you use a class like i did and define your methods as class methods, you work with a single instance everytime : The instance of class “Class” called Color.

  • http://rikkus.info Rik Hemsley

    Of course the Hash should be created in the scope where it’ll be used. That will possibly be in global scope. Why is that a bad idea? I presume you think it’s ok for your class to be in global scope?

    If you’re worried about Colors being a variable, freeze it.

  • http://www.rubyfleebie.com Frank

    An enum must not be considered like a variable, but like a type.

    Plus, if you write : Colors = { :blue => 1, :red => 2, :yellow => 5 }, you’re violating a ruby coding convention which says that you must only use lowercase for naming a local / instance variable.

    So, to be nice you would have to write : colors = { :blue => 1, :red => 2, :yellow => 5 } and thus indicating clearly to the outside world that colors is a “single instance of something” instead of just “being something” on it’s own. That’s why I prefer using an enum-like class over using an instance of Hash class.

  • http://rikkus.info Rik Hemsley

    1. I agree that an enum shouldn’t be considered ‘like a variable’ – but like a type? In Ruby, an object’s type is how it quacks. If I can get the value associated with the word ‘red’ using Colors[:red], the object is of an appropriate type.

    The convention is that variables start with a lower case letter, local or not. Enumerations are (usually) treated as constant, which is why I named Colors with an initial capital.

  • http://www.mr-eel.com Mr eel

    ” a ruby coding convention which says that you must only use lowercase for naming a local / instance variable”

    Actually not a convention at all but a feature of the language. In this case the name ‘Colors’ becomes a constant because it begins with a capital.

    This is an interesting use of const_missing. You’ve given me a bit to think about here.

  • http://www.rubyfleebie.com Frank

    You’re right. My bad on this one.

  • http://nertzy.com Grant Hutchins

    First off, you should use a Module, not a Class, since you’re not going to be instantiating objects.

    Second, I would suggest using a constant hash instead of @hash if it isn’t going to change.

    So I’d suggest:

    module Color
    COLORS = [:blue, :red:, :yellow]
    end

    or instead of defining each at all, just do Color::COLORS.each and it’s built right in.

  • Pingback: Design Decisions < Ardekantur()

  • siroj

    Frank, I think what @Badaud means is enums in C# (I don’t really know about enums in Java, maybe it’s the same, I only used it once a year ago, already forgot).

    In C#, if you declare a method parameter to be some kind of enum, then you have to pass EXACTLY the MEMBER of that enum. So in C#, enums is more than just some constants grouped in one place, but it also has type information. If you pass different kind of enum than the one that method expected, then the compiler will happily report it as error to you.

    But I think is quite impossible to do it in Ruby (actually, I haven’t thinking about it :D), because usually we don’t pay much attention to type information of an object. Event worse, actually Ruby has different concept about type (type-is-all-about-what-an-object-can-do vs type-is-all-about-object-class-hierarchy)

  • http://www.rubyfleebie.com Frank

    Yes, you are right siroj. The enum described in this article was not an enum in the pure sense of the word… as it isn’t a “type” per se.

    Most of the time, I only need the kind of enum described in this article… or I don’t need an enum at all. I just like to “group names under a common namespace” for code readability. I rarely need something fancier.

    Thanks for your comment

  • http://alexegg.com Alex Egg

    Consider this enum of the style you describe:

    class Status
    Complete=0
    Queued=1
    Ready=2
    Processing=3
    Waiting=4
    end

    now say I have an instance of this status:

    status=Status::Waiting

    How could I get this as a string that says “Waiting”? Even thought status is 4. Maybe create a class method in Status called to_string or something?

  • http://www.rubyfleebie.com Frank

    Alex,

    That’s a good question. Instead of using a Fixnum as the value, you could use a Hash :

    class Status
    Complete={:value => 0, :string => “complete”}
    Queued={:value => 1, :string => “queued”}
    Ready={:value => 2, :string => “ready”}
    Processing={:value => 3, :string => “processing”}
    Waiting={:value => 4, :string => “waiting”}
    end

    status = Status::Waiting
    status[:value] # output : 4
    status[:string] # output : waiting

    There might be better ways to achieve what you want, but I think this solution would work fine.

  • http://www.all-x.net Allex

    Look at Renum – “a Ruby enum gem”.

  • http://atomicobject.com Karlin Fox

    You may also be interested in Enumeration: http://github.com/karlin/enumeration/tree/master

  • http://www.rubyfleebie.com Frank

    Hey Thanks for the links!

  • jeff

    Might also take a look at enumerated_attribute: http://github.com/jeffp/enumerated_attribute/tree/master. It works like…

    enum_attr :gear, %w(reverse ^neutral first second third)

  • Brent

    very helpful (even comments), thanks!!

  • Sidy Diop

    Hi,
    Another thing you could do (if you just need the integer values of associated symbols) is:
    Colors = [ RED = 0, YELLOW = 1, GREEN = 3]
    Then you can just loop doing: COLORS.each do { |x| do_something x }
    You can access COLORS[RED]. Of course, COLORS would be a global if you need it everywhere. But again, you can encapsulate it in a module if you wish.

  • http://code.dblock.org dB.

    Let’s make some small changes and make this Enum structure reusable – http://code.dblock.org/ShowPost.aspx?id=184.

  • http://jzinedine.me Jahangir Zinedine

    Thanks, and is there any way to internationalize the designed enum?

  • http://code.dblock.org dB.
  • Pingback: Rails Enum type | Frank Sun()

  • http://jasonwyoung.blogspot.com/ Manther

    Another gem with different flavor. Different implementation, different goals.

    https://github.com/Manther/enumb

  • http://twitter.com/ikenna_okpala Brad

    This design is wicked! You definitely know how to keep a reader
    amused. Between your wit and your videos, I
    was almost moved to start my own blog (well, almost…HaHa!) Fantastic job.
    I really loved what you had to say, and more than that, how you presented it.
    Too cool!

  • http://Top.tessasarcade.info/index.php?a=stats&u=lornasomerset natural Cat Food Recipes

    It’s a shame you don’t have a donate button! I’d definitely donate to this
    excellent blog! I suppose for now i’ll settle for book-marking and adding your RSS feed to my Google account.
    I look forward to new updates and will share this blog with
    my Facebook group. Chat soon!

  • Pingback: Rails Enumerated Types or Alternatives - Zweig Gyan()

  • http://chabrowepole.pl/forum/member.php?action=profile&uid=151187 mistress live chat

    Thank you for some other informative web site. The place else may I get
    that type of information written in such an ideal method?

    I’ve a undertaking that I am simply now running on, and I’ve been at the glance out for such info.

  • http://prezenty-upominki.eu/zegary-reklamowe-w-kreowaniu-wizerunku-firmy/ prezenty-upominki.eu

    Hi friends, how is the whole thing, and what you want to say on the topic of this paragraph, in my view its actually awesome in support
    of me.

  • http://www.youtube.com/watch?v=D8F-w1wFhPM http://www.youtube.com/watch?v=D8F-w1wFhPM

    This article is genuinely a fastidious one it assists new internet people, who are wishing for blogging.

  • Pingback: Rails Enumerated Types or Alternatives | Zephier()

  • Pingback: How to: Enums in Ruby | SevenNet()

  • Pingback: Fixed Enums in Ruby #dev #it #asnwer | Good Answer()

  • Pingback: Ruby on Rails Enumerations and Fixtures – CodeSolid()