Reasonable attempt to do type-safe enum for Ruby?

K

Kedar Mhaswade

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
 
K

Kedar Mhaswade

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

Robert Klemme

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
=A0attr_reader :state

=A0def initialize(s)
=A0 =A0@state =3D s
=A0end

=A0def to_s
=A0 [email protected]_s
=A0end

ACCEPTED =3D GenericState.new:)accepted)
EXPIRED =A0=3D GenericState.new:)expired)
IGNORED =A0=3D GenericState.new:)ignored)
REJECTED =3D GenericState.new:)rejected)
SENT =A0 =A0 =3D GenericState.new:)sent)
UNSURE =A0 =3D GenericState.new:)unsure)
STATES =A0 =3D [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.
=A0class << self
=A0 =A0include Enumerable
=A0 =A0def each(&block)
=A0 =A0 =A0STATES.each(&block)
=A0 =A0end
=A0 =A0alias all entries
=A0end

# convenience methods
=A0def one_of_my_predicates?(meth)
=A0 =A0# meth is a symbol
=A0 =A0$1 if meth =3D~ /(.+)\?$/ && STATES.any?{|ss| ss.to_s =3D=3D $1}
=A0end

=A0def run_predicate(method_name)
=A0 =A0method_name =3D=3D to_s
=A0end

=A0def method_missing(meth, *args, &block)
=A0 =A0matched =3D one_of_my_predicates?(meth)
=A0 =A0if matched
=A0 =A0 =A0run_predicate(matched)
=A0 =A0else
=A0 =A0 =A0super
=A0 =A0end
=A0end

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 =3D=3D GenericState::SENT} # =3D> true
puts GenericState.all # =3D> An array of all GenericState objects!
s1 =3D GenericState::SENT
s2 =3D GenericState::ACCEPTED
s3 =3D GenericState::ACCEPTED
puts s1 =3D=3D s2 # =3D> false
puts s3 =3D=3D s2 # =3D> true
puts s2.object_id; puts s3.object_id #=3D> they are the same objects
puts "Does s1 imply sent? : #{s1.sent?}" # =3D> true
puts "Does s3 imply accepted? : #{s3.accepted?}" # =3D> true
puts s1.to_s #=3D> "sent"

How about

https://gist.github.com/983088

Cheers

robert


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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top