TwitterFacebookGoogle

Cloning an object for creation in Ruby

Ruby provides two methods for creating a copy of an object: clone() and dup(). Starting from Rails 3.1, the behavior of these methods has somewhat changed. In this post, I’ll point out some of the differences as they relate to record creation and also share some helpful pointers when constructing and saving a new object-graph.

Let’s say we’ve got the following classes:

class Unit
  attr_accessible :name, :division_id
  belongs_to :division
 
  validates :name, presence :true
  validates :division, presence :true
 
  validates_uniqueness_of :name, :scope => [:division_id], :case_sensitive => false, :on => :create	 
end
 
class Division
  attr_accessible :name, :user_id
  belongs_to :user
 
  validates :name, presence :true
  validates :user, presence :true
end
 
class User
  #... your good 'ole user class
end

The Scenario

What we’d like to do is create a copy of an existing unit, add it to a brand-new division that belongs to some user and then call user.save(). What we’d expect to see is that:

- A new division is created and associated with the user
- A new unit is created and associated with the newly created division

Understanding clone vs. dup

Let’s first look at the major differences between clone() and dup() so we know which method to use for our specific scenario.

One difference is that clone() does not mark the copy as a new_record while dup() does:

  >> u_clone = Unit.first.clone
  >> u_clone.new_record?
  >> = false
  >>
  >> u_dup = Unit.first.dup
  >> u_dup.new_record?
  >> = true

As a consequence, dup() blanks out the id, created_at and updated_at attributes whereas clone() does not.

Another difference is that clone() copies over the contents plus the internal state and singleton methods whereas dup() only copies over the contents.

Creating and persisting our object graph

In our case, because we wish to create a new unit and add it to a new division, it looks like dup() is what we need. Let’s give it a shot and see what happens:

 >> usr = User.first
 >> d1 = usr.divisions.build({ name: "Divison 1" })
 >> d1.units << Unit.first.dup
 >> usr.save
 >>

What we are doing in the code snippet above is picking the first user, adding a new Division to the user’s divisions collection, adding a new Unit to the divison and then calling save on the user. Think it’ll work? Let’s look at the next line to see what transpired:

  >> usr.save
  >> = false

Oops, didn’t work! Let’s look at the error messages to see what went wrong:

  >> usr.save
  >> = false
  >> usr.errors
  >> [divisions: 'is invalid']
  >> usr.divisions.last.errors
  >> [units: 'is invalid']
  >> usr.divisions.last.units.first.errors
  >> [division: "can't be blank"]

Hmm…division can’t be blank! If you scroll to the class definition of Unit, you’ll notice that we have a validation rule that enforces that division can’t be blank. If we got rid of that validation rule then the above usr.save() would passed. This is because Rails is smart enough to recognize that a new unit is being added to a new division and so it’ll first create the division inside the database, assign the division to unit and the create the unit inside the database.

So, why doesn’t that work with the validation rule being present? Because Rails runs validation on the entire object graph before persisting anything to the database! Since, in our case, we are adding a new unit to a new division, unit.division is nil and hence our validation that states “Unit must have division” fails and nothing is persisted. To make this work without removing the validation rule, what we’ll need to do is add the following additional lines:

 >> u_dup = Unit.first.dup
 >> u_dup.division = d1

Or a better approach is to override the dup() method so that it accepts division as a parameter:

class Unit
   ...
 
  def dup(division) 
    u_dup = super()
    u_dup.division = divison
    u_dup
  end	 
 
  ...
end

And now the following will work successfully:

 >> usr = User.first
 >> d1 = usr.divisions.build({ name: "Divison 1" })
 >> d1.units << Unit.first.dup
 >> usr.save
 >> = true

Hopefully, this gave you some insight into how clone() and dup() work and it’ll save you some headache the next time you need to persist an object graph!

Twitter Email Linkedin Digg Stumbleupon Subscribe

Leave a comment

Your email address will not be published.


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

CyberChimps