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.
For practical purposes, I’m not defining the initializers. So, I have an instance of Child and pass it to Father.
At this point, father have your own copy of child object:
Then, let’s clone our “father”object and call it of “dolly”:
Sounds good, everthing ok.
As we expect, dolly and father are different objects:
So, we decide to manipulate our child value inside our clone object dolly.
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:
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.
Let’s check if everthing is working as we expect now:
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.