tags:

views:

5240

answers:

8
+22  Q: 

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.

+1  A: 

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.

Jan Krüger
+12  A: 

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.

emk
So the Ruby spirit is: "Typos will compile"
Max Howell
Popular Ruby frameworks rely heavily on runtime metaprogramming, and performing too much load-time checking would take away most of Ruby's expressive power. To avoid problems, most Ruby programmers practice test-driven design, which will find not just typos but also logic errors.
emk
@emk, thanks for your comment, it really sums up the dynamic-lang crowd's thought on the thing. While I do think it's wrong, that's how it works :)
Yar
@yar: Well, language design is a series of tradeoffs, and language features interact. If you want a good, highly-dynamic language, go with Ruby, write your unit tests first, and go with the spirit of the language. :-) If that's not what you're looking for, there are dozens of other excellent languages out there, each of which makes different tradeoffs.
emk
@emk, I agree, but my personal issue is that I feel quite comfortable in Ruby, but I do not feel comfortable refactoring in Ruby. And now that I've started writing unit tests (finally), I realize that they are not a panacea: my guess is 1) that Ruby code doesn't get massively refactored that often, in practice and 2) Ruby is not the end-of-the-line in terms of dynamic languages, precisely because it's hard to refactor automatically. See my question 2317579 which got taken over, strangely, by the Smalltalk folks.
Yar
@Max, the way Ruby works, what appears to be a typo at compile time may not be a typo at run time. Using symbols in Ruby is merely an idiomatic and performant way to do this. In a language like C, enums are really the only way to solve this problem. But in C# I see lots of people using strings to solve the "named index" problem rather than taking the time to build an enum. Strings have the same "typos will compile" problems as Ruby's symbols *and* confounds the IDE's autocomplete functionality.
mcl
Yeah, but using those strings would not be in the spirit of the C# language, it is simply a bad practice.
Ed Swangren
@yar: For Ruby (and similar languages) to be truly usable and refactorable, you really do need excellent test coverage. The easiest way to do this is to never add a feature except to fix a failing test case. This will give near-complete coverage, and it completely transforms the experience of working in a dynamic language. And yes, I've done some pretty serious refactorings of Ruby code. They're a bit harder than Eclipse refactorings, but there's usually far fewer lines of code, so it balances out.
emk
@emk, thanks for that: so basically you're replacing compile-time type checks with test-time human-written test code. Except for the fact that the latter is much more comprehensive, it doesn't sound like a very good tradeoff (more work). But I'll let you know what I think after a few months of Ruby with tests.
Yar
@yar, no problem! But even working in static languages, I tend to prefer very comprehensive test suites, because I design my APIs working from the test suites inwards to the actual implementation. So for people who've adopted this style, Ruby is literally no extra work—and we get some cool dynamic features, too. (There are some exceptions to this in my experience, particularly higher-order mathematical code in Haskell, where types _and_ tests are invaluable.) Good luck with Ruby!
emk
+26  A: 

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 class to hold your constants and then declare the constants within that.

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

flags = Foo::BAR | Foo::BAZ # flags = 3
mcl
What if these enum is too be stored to the database? Will symbol notation works? I doubt...
Phương Nguyễn
I would use the constants approach if I were saving to a database. Of course then you have to do some sort of lookup when pulling the data back out of the DB. You could also use something like `:minnesota.to_s` when saving to a database to save the string version of the symbol. Rails, I believe, has some helper methods to deal with some of this.
mcl
i like the 2nd idea very much. thank you!
Labuschin
+1  A: 

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"}>
mislav
Why not use symbols `Set[:a, :b, :c]`?
Yar
+1  A: 

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

Jonke
+2  A: 

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.

dlamblin
I have never needed an enum in Ruby. Symbols and constants are idiomatic and solve the same problems, don't they?
Chuck
Probably Chuck; but googling for an enum in ruby won't get you that far. It will show you results for people's best attempt at a direct equivalent. Which makes me wonder, maybe there's something nice about having the concept wrapped together.
dlamblin
A: 

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

Philippe Monnet
A: 

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

goreorto