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!
Bulk INSERT from within a Rake task
Recently, I had to insert a ton of sample data into our test database to facilitate testing. My first attempt was to use the activerecord-import gem. Looks pretty promising but for some odd reason I just couldn’t get it work from inside a Rake task. It kept inserting data in sequential order instead of doing BULK inserts.
Well, after some R&D, I came across a “simpler” solution that doesn’t require the use of any additional gems. It turns out that all I had to do was wrap all of my inserts inside one transaction. This speeds up the inserts by a HUGE factor when compared with sequential inserts – ~200 ms vs. 1000 ms for about 10 records! An additional benefit of this approach is that all of your model validations still run as they normally would.
Below is a code snippet:
def make_users puts "Creating users..." users = [] 10.times do |n| users << User.new( full_name: "User #{n}", email: "user_#{n}@test.com", password: "foobar", password_confirmation: "foobar") end User.transaction do users.each { |u| u.save } end puts "Finished creating users." end
Bye bye, activerecord-import gem! I am sticking with the transaction wrapper!


