Skip to content

Instantly share code, notes, and snippets.

@h0rs3r4dish
Created December 24, 2009 00:54
Show Gist options
  • Save h0rs3r4dish/262937 to your computer and use it in GitHub Desktop.
Save h0rs3r4dish/262937 to your computer and use it in GitHub Desktop.
Enums, the Ruby Way
class Enum; class << self; def new(*list)
i = (list[0].class == Fixnum) ? list.shift : 0
Class.new.class_eval do
list.each { |item| const_set item, i; i += 1 }
class << self;
def each(&blk); self.constants.each { |i| blk.call(i) }; nil; end
def map(&blk); self.constants.map { |i| blk.call(i) }; end
end
self
end
end; end; end
# So, the code itself is ten lines. But these comments add quite a bit of
# padding, just for readability's sake. The semicolons are all over the place
# because otherwise I feel like I'm wasting lines. Which doesn't matter, really:
# \n is the same as ; storage-wise. But that's just me.
# Start off by defining the Enum class and then immediately jumping up to
# the metaclass to re-define Enum.new
class Enum; class << self; def new(*list)
# Figure out what value to start at. It defaults to 0, but if you want it to
# be something else, then pass it as the first argument of the new() method
i = (list[0].class == Fixnum) ? list.shift : 0
# This block does all of the hard work. First it creates a new Class (just
# like the `class` keyword does), without a name. Then it runs #class_eval
# over a block, which is a dynamic way to add stuff to a class (again, like
# a person normally does within `class...end`
Class.new.class_eval do
# This one-liner runs through the list of enum members and starts to assing
# them values in the newly-created anonymous class, starting at 0 or the
# starting value passed to Enum.new
list.each { |item| const_set item, i; i += 1 }
# Hop into the anonymous class's metaclass for some method definitions, so
# you can use . instead of :: (feels better, trust me!)
class << self;
# Iterators galore! Since all an Enum is is a collection of constants,
# both of these methods just iterate over the rather convenient array
# that Object#constants provides. Note that Enum#each returns nil, while
# Enum#map returns an array
def each(&blk); self.constants.each { |i| blk.call(i) }; nil; end
def map(&blk); self.constants.map { |i| blk.call(i) }; end
end
# Override Ruby's implicit return, which would normally give us back some-
# things strange from the `def map()..end` block, and return the new class.
# It's best to assign the result of Enum.new to a constant, so you have a
# name and everything to go along with it.
self
end
end; end; end

Ruby enums. They're not meant to be. Otherwise, they would probably be in the Standard Library, no? After all, Struct is in there. But sometimes, you just gotta have something a bit more concrete than a symbol.

Enter this class. The Enum class works almost exactly like the Struct one does: you pass it a list of symbols, and it hands you back a class. The trick is, you don't make any instances of the class; it is the enum. Allow me to demonstrate:

Lives = Enum.new(:NONE, :ONE, :TWO, :MORE)
Lives.each { |element| puts element } # => "NONE\nONE\nTWO\nMORE"
Lives::NONE # => 0

That's all there is to it. Klass::Constant is how things work (assuming you used Klass = Enum.new(:Constant)). There's Klass.each and Klass.map too, if you feel like cycling through them.

What if you wanted something... fancier?

enum {
  ALPHA = 1
  BRAVO
} // This is psuedo-C, by the way

Well, we can work with that:

Callsigns = Enum.new(1, :ALPHA, :BRAVO)
Callsigns::ALPHA # => 1, not 0

Now, kick back, relax, and enjoy the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment