Friday, November 02, 2007

Renum -- a Ruby enum gem

A while back I blogged through trying to implement something in Ruby similar to what you get from Java and C#'s enum keyword (and before there was a keyword, from the type-safe enumeration pattern in Java). It went pretty well.

I mentioned there that I might go ahead and package the thing (or something like it) up as a gem. Tonight I did. It's called Renum.

Renum lets you create enums like this:

enum :Status, [ :NOT_STARTED, :IN_PROGRESS, :COMPLETE ]

enum :Color, [ :RED, :GREEN, :BLUE ] do
  def abbr
    name[0..0]
  end
end

As of version 0.0.1, released just minutes ago, those enums work like this:

describe "enum" do
  
  it "creates a class for the value type" do
    Status.class.should == Class
  end
  
  it "makes each value an instance of the value type" do
    Status::NOT_STARTED.class.should == Status
  end
  
  it "exposes array of values" do
    Status.values.should == [Status::NOT_STARTED, Status::IN_PROGRESS, Status::COMPLETE]
  end
  
  it "enumerates over values" do
    Status.map {|s| s.name}.should == %w[NOT_STARTED IN_PROGRESS COMPLETE]
  end
  
  it "indexes values" do
    Status[2].should == Status::COMPLETE
    Color[0].should == Color::RED
  end
  
  it "provides index lookup on values" do
    Status::IN_PROGRESS.index.should == 1
    Color::GREEN.index.should == 1
  end
  
  it "allows an associated block to define instance methods" do
    Color::RED.abbr.should == "R"
  end
  
  it "provides a reasonable to_s for values" do
    Status::NOT_STARTED.to_s.should == "Status::NOT_STARTED"
  end
  
end

Hopefully someone will find this useful. There are some obvious features to add, like value lookup by name. There are also a lot of things that could be locked down a little, but the library's useful without that so I'm not sure it's worthwhile. (For example, I don't currently prevent you calling Color.new 'HAZEL'.)

If this is something you might use, let me know what you want out of it.

Also, tonight I arrived in Charlotte for RubyConf. Say hi if you see me.

7 comments:

Kris Nuttycombe said...

Nice gem! However, I have a question about how the usage with the block works. With a Java enum, it's possible to define different behavior for each enumerated value - for example, your "abbr" method could have a different implementation for each constant. Does renum have a way to do that without a switch in the defined method?

John Hume said...

That's not supported yet, but it ought to be. I'll put that on my todo list for the holiday break. Thanks.

ThynksDepot said...

Thank You! Great Info!

Roger Pack said...

thanks for moving ruby forward

karlin said...

I've made a similar library available called Enumeration:

Colors = Enumeration.of :red, :blue, :green

http://github.com/karlin/enumeration/tree/master

John Hume said...

Note that as of a few days ago Renum now lives at github: http://github.com/duelinmarkers/renum.

Jeff said...

You might like the enumerated_attribute gem on github: http://github.com/jeffp/enumerated_attribute/tree/master.

It works like this:

enum_attr :mode, %w(off sleeping hibernating running)