Extending the behavior to clone objects in Ruby

A few days ago, I came across an unexpected behavior in my codebase. Basically one of my Ruby Class has an a collection of objects as attribute, but when processed by instances of other classes, some objects from my collection were changed and some side effects happened (mutations).

Let’s draft an example

I’ll write now a very simple code just to provide you a clear vision of the problem. If you want, open your IRB and start type the code below with me.

Let’s create two simple class, Father and Child. Child has an attribute “name” and Father takes a child.

1
2
3
4
5
6
7
class Father
attr_accessor :child
end
class Child
attr_accessor :name
end

For practical purposes, I’m not defining the initializers. So, I have an instance of Child and pass it to Father.

1
2
3
4
5
child = Child.new
child.name = "Mr. White"
father = Father.new
father.child = child

At this point, father have your own copy of child object:

1
2
3
father.child.name #=> "Mr. White"
father.child.onject_id == child.object_id # => true

Then, let’s clone our “father”object and call it of “dolly”:

1
2
3
dolly = father.clone
dolly.child.name #=> "Mr. White"

Sounds good, everthing ok.

As we expect, dolly and father are different objects:

1
father.object_id == dolly.object_id # => false

So, we decide to manipulate our child value inside our clone object dolly.

1
2
3
dolly.child.name = "Jesse Pinkman"
dolly.child.name #=> "Jesse Pinkman"

And finally we’re able to see the problem proposed here.

We’re expecting to our original object “father” should have “completely” cloned and their original state preserved, right? However, when we set the “child.name” attribute to “Jesse Pinkman” on “dolly”, we’re also setting at the same time this value to “child.name” on “father” object. Take a look:

1
2
3
father.child.name #=> "Jesse Pinkman"
father.child.object_id == dolly.child.object_id # => true

father#child and dolly#child has the internal reference for the same object. Pretty cool, huh?

And if we try to use dup instead of clone? We’ll get the same results. Make your own test if you want.

initialize_copy to the rescue

Our alternative here is define inside our Father class an initialize_copy method, what will provide to us a politely way to customize our process of “cloning” our objects, and we be able to do what we want to reach the expected behavior.

1
2
3
4
5
6
7
8
class Father
attr_accessor :child
def initialize_copy(cloning)
super
self.child = cloning.child.clone
end
end

Let’s check if everthing is working as we expect now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
child = Child.new
child.name = "Mr. White"
father = Father.new
father.child = child
dolly = father.clone
dolly.child.name = "Jesse Pinkman"
dolly.child.name #=> "Jesse Pinkman"
father.child.name #=> "Mr. White"
father.child.object_id == dolly.child.object_id # => false

Perfect, that was exactly what we’re expecting to happens!

I sincerly hope now if you’re using any technique, like Dependency Injection for example, and got some unexpected behaviour similar with I described here you can remember that is possible to extend the way how Ruby clone objects using initialize_copy and personalize it to meet your requirements.

avatar

Tailor Fontela

Software Developer. Full-time apprenticeship.