I’m deeply convinced that I’m a lion..

The Complete Guide to Create a Copy of an Object in Ruby: Part II

Tech - RubyCademy
RubyCademy
Published in
4 min readJan 7, 2019

--

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

  • the #initialize_copy method
  • the Marshal class
  • the #deep_dup method in Rails

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

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 method 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 Fighter#initialize_copy 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 at the other alternatives.

The Marshal class

Object Marshalling is the concept of formatting the in-memory representation of an object to make it suitable for storage & deserialization.

Basically, for a given object:

  • we serialize all its attribute values
  • we store the serialization (for example, in a file or a variable)
  • we deserialize it at any time and get back a new instance of the serialized object with the same values.

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

Let’s have a look at 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 serialize this instance by using the Marshal.dump method.

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

Finally, we deserialize 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 serialize the khabib instance of Fighter by using the Marshal.dump method.

Then we deserialize 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 instances of Title.

There can be 2 main concerns to processing a deep copy by using object marshaling:

  • it can become a very slow operation at scale
  • it’s not fully working with complex objects.

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

Now let’s have a look at 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 a 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 are only a few classes that are not “duplicable”:

NilClass FalseClass TrueClass Symbol Numeric BigDecimal Method Complex Rational

2- Object#deep_dup 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..).

Ruby Mastery

We’re currently finalizing our first online course: Ruby Mastery.

Join the list for an exclusive release alert! 🔔

🔗 Ruby Mastery by RubyCademy

Also, you can follow us on x.com as we’re very active on this platform. Indeed, we post elaborate code examples every day.

💚

--

--