From: Robert Klemme Date: 2011-05-21T00:02:35+09:00 Subject: Re: Reasonable attempt to do type-safe enum for Ruby? On Fri, May 20, 2011 at 4:35 PM, Kedar Mhaswade wrote: > Perhaps this has been tried before. But I didn't find it. Google > searches showed many different ways to implement an "enum" data type for > Ruby. Searching on forum suggested using hashes, arrays and Struct. But > none of them seemed type-safe to me. So, I went ahead and did something > like below, for an enum named GenericState which represents a "state" of > some kind. > > Please comment on this approach. > > > # An enum kind of model. No persistent equivalent. > # I still don't know the correct way of doing enums in Ruby! > class GenericState >  attr_reader :state > >  def initialize(s) >    @state = s >  end > >  def to_s >    @state.to_s >  end > > ACCEPTED = GenericState.new(:accepted) > EXPIRED  = GenericState.new(:expired) > IGNORED  = GenericState.new(:ignored) > REJECTED = GenericState.new(:rejected) > SENT     = GenericState.new(:sent) > UNSURE   = GenericState.new(:unsure) > STATES   = [ACCEPTED, EXPIRED, IGNORED, REJECTED, SENT, UNSURE] There is definitively too much redundancy here. STATES is superfluous since you can use method constants for that. Also, you repeat the name. >  class << self >    include Enumerable >    def each(&block) >      STATES.each(&block) >    end >    alias all entries >  end > > # convenience methods >  def one_of_my_predicates?(meth) >    # meth is a symbol >    $1 if meth =~ /(.+)\?$/ && STATES.any?{|ss| ss.to_s == $1} >  end > >  def run_predicate(method_name) >    method_name == to_s >  end > >  def method_missing(meth, *args, &block) >    matched = one_of_my_predicates?(meth) >    if matched >      run_predicate(matched) >    else >      super >    end >  end > > end > > The method_missing protocol does not have to be implemented, but it > facilitates more readable usage as indicated below. > > #usage > puts GenericState.any? {|x| x == GenericState::SENT} # => true > puts GenericState.all # => An array of all GenericState objects! > s1 = GenericState::SENT > s2 = GenericState::ACCEPTED > s3 = GenericState::ACCEPTED > puts s1 == s2 # => false > puts s3 == s2 # => true > puts s2.object_id; puts s3.object_id #=> they are the same objects > puts "Does s1 imply sent? : #{s1.sent?}" # => true > puts "Does s3 imply accepted? : #{s3.accepted?}" # => true > puts s1.to_s #=> "sent" How about https://gist.github.com/983088 Cheers robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/