Reasonable attempt to do type-safe enum for Ruby?

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]

  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"

Best Regards,
Kedar

···

--
Posted via http://www.ruby-forum.com/.

I need to have constructor private like:
class GenericState
private_class_method :new
# reminder ...
end

···

--
Posted via http://www.ruby-forum.com/.

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

Cheers

robert

···

On Fri, May 20, 2011 at 4:35 PM, Kedar Mhaswade <kedar.mhaswade@gmail.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Yet another enum... · GitHub

Wow. This is meta programming on steroids :-). Thanks, will study it
more.

···

--
Posted via http://www.ruby-forum.com/\.