David Brady wrote:

> In C++, I would use an enum for each of the values, then build arrays of 
> floats and strings indexed by those enums to hold the thresholds and 
> descriptions.

That's also possible in Ruby, but I think it is a better idea to 
aggregate all the data into the enum member itself.

Here's Ruby code for doing C++ style enums:

> # Represents a C# style enumeration of known values.
> #
> # Usage:
> #   Color = Enum.new(:Red, :Green, :Blue)
> #   Color.is_a?(Enum) # => true
> #   Color::Red.inspect # => "Color::Red"
> #   Color::Green.is_a?(Color) # => true
> #   Color::Green.is_a?(Enum::Member) # => true
> #   Color::Green.index # => 1
> #   Color::Blue.enum # => Color
> #   values = [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
> #   values[Color::Green] # => [0, 255, 0]
> #   Color[0] # => Color::Red
> #   Color.size # => 3
> #
> # Enums are enumerable. Enum::Members are comparable.
> class Enum < Module
>   class Member < Module
>     attr_reader :enum, :index
> 
>     def initialize(enum, index)
>       @enum, @index = enum, index
>       # Allow Color::Red.is_a?(Color)
>       extend enum
>     end
> 
>     # Allow use of enum members as array indices
>     alias :to_int :index
>     alias :to_i :index
> 
>     # Allow comparison by index
>     def <=>(other)
>       @index <=> other.index
>     end
> 
>     include Comparable
>   end
> 
>   def initialize(*symbols, &block)
>     @members = []
>     symbols.each_with_index do |symbol, index|
>       # Allow Enum.new(:foo)
>       symbol = symbol.to_s.sub(/^[a-z]/) { |letter| letter.upcase }.to_sym
>       member = Enum::Member.new(self, index)
>       const_set(symbol, member)
>       @members << member
>     end
>     super(&block)
>   end
> 
>   def [](index) @members[index] end
>   def size() @members.size end
>   alias :length :size
> 
>   def first(*args) @members.first(*args) end
>   def last(*args) @members.last(*args) end
> 
>   def each(&block) @members.each(&block) end
>   include Enumerable
> end

But I think it is not the best match in this case.

> Struct.new("RatingData", name, threshold, description)
> 
> RATING_GOOD = Struct::RatingData( :Good, 0.8, "Good Rating" )
> RATING_FAIR = Struct::RatingData( :Fair, 0.5, "Fair Rating" )

This sounds good, but I'd go with a module:

module Rating
   Good = Struct::RatingData.new(:Good, 0.8, "Good Rating")
   Fair = Struct::RatingData.new(:Fair, 0.5, "Fair Rating")
end

That seems more Rubyish to me.