Skip to content

Instantly share code, notes, and snippets.

@ootoovak
Last active December 18, 2015 16:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ootoovak/5811499 to your computer and use it in GitHub Desktop.
Save ootoovak/5811499 to your computer and use it in GitHub Desktop.
Don't recreate another object if you don't need to.
# Want to pass in a hash or instance of object. Was hoping to avoid is_a? or creating a new object but
# think that can only be done with is_a? or overriding new. Also, I might be missing the point as
# avoiding is_a? should mean I am using Duck Typing but if I'm using Duck Typing then I shouldn't be
# checking object equality anyway. See RSpec test for details.
describe MyClass do
let(:hash) { { name: 'Duck Type', description: 'Quacks like a duck?' } }
before do
@my_instance_1 = MyClass.new(hash)
@my_instance_2 = MyClass.new(@my_instance_1)
end
it 'this is preferable' do
@my_instance_1.should equal(@my_instance_2)
end
it 'this is ok' do
@my_instance_1.should eq(@my_instance_2)
end
end
# Possilbe solutions involve overriding MyClass.new or buidling a constructor
# http://stackoverflow.com/questions/4888786/how-can-i-change-the-return-value-of-a-class-constructor-in-ruby
# Good old Rubber Duck Debugging
# Will only pass 1 of 2 tests
class MyClass
attr_accessor :name
attr_accessor :description
def initialize(args = {})
@name = args.fetch(:name, '')
@description = args.fetch(:description, '')
end
def fetch(key, default_value)
send(key) || default_value
end
end
# This one works as I originally wanted but requires a Class check and modifying new with I'm
# pretty sure can lead to bad things given how Ruby handles new and initialize.
class MyClass
attr_accessor :name
attr_accessor :description
def self.new(args = {})
args.is_a?(MyClass) ? args : super(args)
end
def initialize(args = {})
@name = args.fetch(:name, '')
@description = args.fetch(:description, '')
end
def fetch(key, default_value)
send(key) || default_value
end
end
@ootoovak
Copy link
Author

I'm starting to convince myself that I was thinking about wanting duck typing on one side (removing conditional and not checking on class) but not thinking about it on the other side (not wanting to have to make another object and so object_id equality). Unless I am missing something I don't think there is a way I can have both.

@eoinkelly
Copy link

That seems like a good solution to allowing args as Hash or object but I don't think getting equal() to pass is what you need.

equal() will only pass if both variables refer to the exact same object in memory e.g.

2.0.0p195 :030 > a = Array.new
[]
2.0.0p195 :031 > b = Array.new
[]
2.0.0p195 :032 > a.object_id
74420560
2.0.0p195 :033 > b.object_id
74429320
2.0.0p195 :034 > a.equal? b
false
2.0.0p195 :035 > a.object_id == b.object_id
false
2.0.0p195 :036 > a = b
[]
2.0.0p195 :037 > a.object_id
74429320
2.0.0p195 :038 > b.object_id
74429320
2.0.0p195 :039 > a.equal? b
true
2.0.0p195 :040 > a.object_id == b.object_id
true

so even if both objects have exactly the same stuff in them, equal() will not pass unless they are actually references to the same bit of the heap.

I think that if your tests check that the attributes are the same whether initialize() was given a hash or an object then you have won ;-)

@ootoovak
Copy link
Author

@eoinkelly yeah that is what I was going for at first. I wanted to just send back a pointer to that object if that object was already an instance of that class. Thinking it would save CPU and Mem by not copying the values over from one instance to another. Not that it is a huge deal (CPU/Mem) in my case but was more wondering if it could be done.

@eoinkelly
Copy link

Ah ok. In order to memoize the return value, you will need to keep track of all instances of that class that have been created in the system (so you can return an existing one if it matches the parameters). You could probably make MyClass store a reference to every instance it creates and then return one of them from initialize. That said, I think having a separate MyClassFactory that does this magic would be cleaner.

BTW your example makes name and description mutable. This is probably a bad idea as you will couple every user of one of these objects together in a potentially hilarious way.

Thanks for the chewy problem ;-)
HTH
/Eoin/

@ootoovak
Copy link
Author

@eoinkelly good catch. I'll look into that potential coupling. Guess I clone values to be save?

Also, would be keen on seeing how you would write your solution if you had a chance to fork this Gist sometime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment