I’m deep ly convinced that I’m a lion..

In this article, we’re going to explore the following topics:

the #initialize_copy method

method the Marshal class

class the #deep_dup method in Rails

Have a quick look to the Part I (3 minutes read)

Before to start

I’m thrilled to share with you our latest project: Fun Facts about Ruby — Volume 1

Please feel free to spread the word and share this post! 🙏

Thank you for your time!

Introduction

Ruby doesn’t handle deep copy by default.

Indeed, it only handles shallow copy through the Object#clone and Object#dup methods.

But what if we still need to make a deep copy of an object?

In this article, we’ll detail 3 ways to make a deep copy of an object in Ruby.

The #initialize_copy method

The #initialize_copy hook can be used to interact with the freshly created copy of a given object.

We can tweak this hook to achieve a deep copy.

First, let’s recap how to achieve a shallow copy in Ruby

Here the problem is that khabib and tony_fergusson share the same belt — UFC Lightweight Champion .

This means that if khabib changes the title.name attribute then the change will be propagated to the tony_fergusson.title.name one — as they share the same Title instance.

So, let’s implement the Fighter#initialize_copy to make a deep copy of khabib

In the above example, we define a Fighter#initialize_copy method that assigns a shallow copy of the original_fighter.title ( khabib ) to self.title ( tony_fergusson ).

So, if we compare the object_id of the 2 titles we can see that they’re now 2 different instances of Title .

That works just fine !

But the problem here is that we “tweak” a method that is called in the context of a shallow copy (via #dup and #clone ) in order to process a deep copy.

Of course, it’s not a serious issue !

But let’s have a look to the other alternatives.

The Marshal class

Object Marshalling is the concept of formatting the memory representation of an object to make it suitable to storage & deserialisation.

Basically, for a given object:

we serialise all its attribute values

we store the serialisation (for example, in a file or a variable)

we deserialise it at any time and get back a new instance of the serialised object with the same values.

In Ruby, the Object Marshalling logic is mainly implemented in the Marshal class.

Let’s have a look to the following example to detail how this class works

In the above example, we can see that the jd variable contains an instance of User .

Then we serialise this instance by using the Marshal.dump method.

This method returns a string that is the serialisation of the memory representation of the jd variable.

Finally, we deserialise this string by using the Marshal.load method.

This method returns a new instance of User that contains the exact same values as jd .

As we can see, jd and other_jd are two distinct instances of User .

Now, let’s see how to process a deep copy using Object Marshalling .

To do so, let’s refactor the Fighter and Title classes example

Here, we serialise the khabib instance of Fighter by using the Marshal.dump method.

Then we deserialise it by using the Marshal.load method and we store the result in the tony_ferguson variable.

As we can see, we achieve the same result as with the #initialize_copy method.

In effect, the title attribute of khabib and tony_fergusson are two distinct instance of Title .

There can be 2 main concerns to process a deep copy by using object marshalling:

it can become a very slow operation at scale

it’s not fully working with complex objects.

Otherwise, it’s a quite efficient way to achieve deep copy in Ruby.

Now let’s have a look to what Ruby on Rails proposes to handle deep copy.

Note that I will dig into Object Marshalling in Ruby in another article.

The #deep_dup method in Rails

The Ruby on Rails framework provides the Object#deep_dup method that allows you to create a deep copy of a given object.

This solution is implemented in ~30 LOC

1- The Object#duplicable? method returns true .

This means that an instance that contains the Object class in its ancestor chain is eligible to deep copy.

There is only few classes that are not “duplicable”:

NilClass FalseClass TrueClass Symbol Numeric BigDecimal Method Complex Rational

2- The Object#deep_dup method returns the return value of the dup method call or self if the object is not eligible to deep copy.

3- The Array#deep_dup method map through the calling array and calls #deep_dup on each element of the array.

4- The Hash#deep_dup method dup the calling hash and iterates through the calling hash.

Then, for each pair, it checks if the key is a frozen object.

If so, then only the value is #deep_dup .

Otherwise, the key and the value are #deep_dup .

There is also a set of #initialize_copy methods that are defined to handle complex objects (in ActiveRecord::Relation , etc..).

Voilà!

ONE MORE THING ⬇

Feel free to subscribe here: www.rubycademy.com