tags:

views:

157

answers:

5

This seems a ridiculously simple question to be asking, but what's the shortest/most idiomatic way of rewriting this in Ruby?

if variable == :a or variable == :b or variable == :c or variable == :d # etc.

I've previously seen this solution:

if [:a, :b, :c, :d].include? variable

but this isn't always functionally equivalent - I believe Array#include? actually looks to see if the variable object is contained in the list; it doesn't take into account that the object may implement its own equality test with def ==(other).

[EDIT: As observed by helpful commentators below, that explanation isn't correct. #include? does use == but it uses the == method of the items in the array - in my example, the symbols - rather than the == method of variable. That explains why it's not equivalent to my first code example.]

(Take, for example, Rails' Mime::Type implementation: request.format == :html may return true, but [:html].include?(request.format) will return false, as request.format is an instance of Mime::Type, not a symbol.)

The best I have so far is:

if [:a, :b, :c, :d].select {|f| variable == f}.any?

but it seems somewhat cumbersome to me. Any better suggestions?

+1  A: 

I've always used your second example, if [:a, :b, :c, :d].include? variable. While this does present some problems with classes that overwrite ==, it's perfectly fine in most situations I've needed it (usually checking against symbols).

Matt Grande
Thanks for your comments. I got bitten today by [:html, :xml].include?(request.format) always returning false in a Rails app, so I was thinking I might need to get myself out of that habit!
NeilS
Perhaps you could write `[:html, :xml].include?(request.format.to_sym)`
glenn jackman
+1  A: 

How about:

 if [:a, :b, :c, :d].index variable
khelll
Thanks for your comment, it's appreciated. I think that exhibits the same behaviour as #include?, though - presumably for the same reason Pesto explains in his comment.
NeilS
A: 

What you are really doing is seeing if variable is a member of the set [:a,:b,:c,:d] so semantically that maps to the include? method. The problem you encounter is due to the duck typing nature of ruby so if you want to get around that why not first convert to a symbol:

if [:a, :b, :c, :d].include? variable.to_sym

ennuikiller
Thanks for your comment, it's apprecited. That works in a lot of cases, but of course won't work if the object in question implements further logic in its definition of equality. (Perhaps it also compares against a list of synonyms, for example.)
NeilS
+3  A: 

It looks like Array#include? does use ==.

>> class AString < String
>>   def ==(other)
>>     self[0] == other[0]
>>   end
>> end

>> asdf = AString.new "asdfg"
=> "asdfg"
>> b = AString.new 'aer'
=> "aer"

>> asdf == b
=> true

>> [asdf].include? b
=> true

Array#include? documentation also explains this: http://ruby-doc.org/core/classes/Array.html#M002203

zgchurch
Thanks for your analysis; it's appreciated. I was being fooled by the problem Pesto observes above, hence my confusion.
NeilS
+15  A: 

Actually, #include? does use ==. The problem arises from the fact that if you do [:a].include? foo, it checks :a == foo, not foo == :a. That is, it uses the == method defined on the objects in the Array, not the variable. Therefore you can use it so long as you make sure the objects in the array have a proper == method.

In cases where that won't work, you can simplify your statement by removing the select statement and passing the block directly to any:

if [:a, :b, :c, :d].any? {|f| variable == f}
Pesto
Aha! I think you've nailed my #include? problem - thank you, that makes perfect sense.Back to the problem at hand, I also like your method of passing a block to any? - that looks very clean to me.
NeilS