Ruby Set with custom class to equal basic strings

Set is built upon Hash, and Hash considers two objects the same if:

[...] their hash value is identical and the two objects are eql? to each other.

What you are missing is that eql? isn't necessarily commutative. Making Rank#eql? recognize strings doesn't change the way String#eql? works:

one.eql?('one') #=> true
'one'.eql?(one) #=> false

Therefore it depends on which object is the hash key and which is the argument to include?:

Set['one'].include?(one) #=> true
Set[one].include?('one') #=> false

In order to make two objects a and b interchangeable hash keys, 3 conditions have to be met:

  1. a.hash == b.hash
  2. a.eql?(b) == true
  3. b.eql?(a) == true

But don't try to modify String#eql? – fiddling with Ruby's core classes isn't recommended and monkey-patching probably won't work anyway because Ruby usually calls the C methods directly for performance reasons.

In fact, making both hash and eql? mimic name doesn't seem like a good idea in the first place. It makes the object's identity ambiguous which can lead to very strange behavior and hard to find bugs:

h = { one => 1, 'one' => 1 }
#=> {#<struct Rank name="one">=>1, "one"=>1}

# vs

h = { 'one' => 1, one => 1 }
#=> {"one"=>1}

Tags:

Ruby