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!

