3

I need to create a class where if the attribute value is the same it does not generate a new object id, example:

result:

described_class.new('01201201202')

<PixKey:0x00007eff5eab1ff8 @key="01201201202">

if i run it again with the same value it should keep the same object id

0x00007eff5eab1ff8

is similar behavior with the symbol

test:

describe '#==' do
  let(:cpf)   { described_class.new('01201201202') }

  it 'verifies the key equality' do
    expect(cpf).to eq described_class.new('01201201202')
  end
 end

Running the test shows an error, because the obejct id changes:

expected: #<PixKey:0x00007eff5eab1ff8 @key="01201201202">
got: #<PixKey:0x00007eff5eab2070 @key="01201201202">

Class:

class PixKey
  def init(key)
    @key = key
  end
end
Davi Luis
  • 396
  • 3
  • 16
  • 2
    According to your test, the objects should merely be equal in regard to `==`. The actual task is probably to implement a custom `==` method. – Stefan Jul 11 '22 at 16:06

4 Answers4

4

The other answers are fine, but they are a little more verbose than needed and they use class variables, which I find to be a confusing concept because of how they are shared among various classes.

class PixKey
  @instances = {}
  def self.new(id)
    @instances[id] ||= super(id)
  end
  def initialize(id)
    @key = id
  end
end

p PixKey.new(1)
p PixKey.new(2)
p PixKey.new(2)
p PixKey.new(1)
David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • Yep, one of Ruby's weakest points is the distinction between instance variables on classes and class variables. I've been a Ruby coder for nearly a decade and still don't fully grok the difference. This is probably a better answer, due to sheer simplicity. – Silvio Mayolo Jul 11 '22 at 15:32
3

Running the test shows an error, because the object id changes

Not quite. It shows an error because the objects are not equal. And the error message prints both objects including their id. But the object id is not what's causing the test to fail.

I need to create a class where if the attribute value is the same it does not generate a new object id

That would probably work, but you're likely approaching the problem from the wrong side. In Ruby, equality doesn't mean object identity. Two objects can be equal without being the same object, e.g.

a = 'foo'
b = 'foo'

a.object_id == b.object_id
#=> false

a == b
#=> true

There's no need to tinker with object ids to get your test passing. You just have to implement a custom == method, e.g.:

class PixKey
  attr_reader :key

  def initialize(key)   # <- not "init"
    @key = key
  end

  def ==(other)
    self.class == other.class && self.key == other.key
  end
end

The == method checks if both objects have the same class (i.e. if both are PixKey instances) and if their key's are equal.

This gives:

a = PixKey.new('01201201202')
b = PixKey.new('01201201202')

a == b
#=> true
Stefan
  • 109,145
  • 14
  • 143
  • 218
1

Create a class method to create instances and have it look up a hash.

class PixKey
   @@instances = {}
   def PixKey.create(id)
       if not @@instances.has_key?(id) 
           @@instances[id] = PixKey.new(id)
       end
       return @@instances[id]
   end
   def initialize(id)
     @key = id
   end
end

a = PixKey.new(123)
b = PixKey.new(123)
c = PixKey.create(123)
d = PixKey.create(123)

puts a
puts b
puts c
puts d

Output:

#<PixKey:0x000000010bc39900>
#<PixKey:0x000000010bc38078>
#<PixKey:0x000000010bc33eb0>
#<PixKey:0x000000010bc33eb0>

Notice the last two instances created with the PixKey.create(id) method return the same instance.

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
1

Note that Ruby's new method is just a method on Class and can be overridden like any other. The docs describe the default implementation.

Calls allocate to create a new object of class's class, then invokes that object's initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.

So, if you want to keep the .new syntax and still get the same objects back, we can override new on the class and call super. This is exactly what OscarRyz' answer does, just with .new and super rather than a separate helper function.

class PixKey
   @@instances = {}
   def PixKey.new(id)
       if not @@instances.has_key?(id)
           @@instances[id] = super(id)
       end
       return @@instances[id]
   end
   def initialize(id)
     @key = id
   end
end

a = PixKey.new(123)
b = PixKey.new(123)

puts a
puts b
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116