-
-
Save ootoovak/5811499 to your computer and use it in GitHub Desktop.
# 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 |
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 ;-)
@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.
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/
@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.
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.