tags:

views:

68

answers:

4

If I create two String instances with the same content separately they are identical. This is not the case with custom classes by default (see example below).

If I have my own class (Test below) and I have a variable (@v below) which is unique, ie. two Test instances with the same @v should be treated as identical, then how would I go about telling Ruby this is the case?

Consider this example:

class Test
  def initialize(v)
    @v = v
  end
end

a = {Test.new('a') => 1, Test.new('b') => 2}

a.delete(Test.new('a'))

p a
# # Desired output:
# => {#<Test:0x100124ef8 @v="b">=>2}
+1  A: 

You need to define an == method that defines what equality means for your class. In this case, you would want:

class Test
  def initialize(v)
    @v = v
  end
  def ==(other)
    @v == other.instance_variable_get(:@v)
  end
end
Chuck
Fantastic, thanks! this will definitely work, but if I change an attribute on `a1 = Test.new('a')` will it also be apparent on `a2 = Test.new('a')`? I really want them to *be* the same object, not just equate.
JP
Oops, I spoke too quickly, adding that `==` method to my example above doesn't actually give the desired output - sorry!
JP
+1  A: 

You are using objects of class Test as keys for the hash. In order for that to function properly (and consequently a.delete), you need to define two methods inside Test: Test#hash and Test#eql?

From: http://ruby-doc.org/core/classes/Hash.html

Hash uses key.eql? to test keys for equality. If you need to use instances of your own classes as keys in a Hash, it is recommended that you define both the eql? and hash methods. The hash method must have the property that a.eql?(b) implies a.hash == b.hash.

Flavius Stef
+1  A: 

I found a different way to tackle this, by keeping track of all the instances of Test internally I can return the premade instance rather than making a new one and telling ruby they're equivalent:

class Test
  def self.new(v)
    begin
      return @@instances[v] if @@instances[v]
    rescue
    end

    new_test = self.allocate
    new_test.instance_variable_set(:@v,v)
    (@@instances ||= {})[v] = new_test
  end
end

Now Test.new('a') == Test.new('a') and Test.new('a') === Test.new('a') :)

JP
+1  A: 

Most of the time, an object you need to be comparable and/or hashable is composed of member variables which are either primitives (integers, strings, etc) or are themselves comparable/hashable. In those cases, this module:

module Hashable

  include Comparable

  def ==(other)
    other.is_a?(self.class) && other.send(:parts) == parts
  end
  alias_method :eql?, :==

  def hash
    parts.hash
  end

end

can simply be included in your class to take care of all of the busywork. All you have to do is define a "parts" method that returns all of the values that comprise the object's state:

class Foo

  include Hashable

  def initialize(a, b)
    @a = a
    @b = b
  end

  private

  def parts
    [@a, @b]
  end

end

Objects built this way are comparable (they have <, <=, ==, >=, >, != and equ?) and they can be hash keys.

Wayne Conrad