1

I have checked a bunch questions on this matter, including here, here, and here. I can't seem to figure out what is going wrong here.

Here is my copy method:

def copy(new_period)
  copy = self.dup
  copy.report_id = Report.maximum(:report_id).next
  copy.period_id = new_period
  copy.responses = self.responses.dup
  copy.save
end

This method correctly makes a copy of the Report model and assigns it to the new period as expected. It also moves all the children from the original report to the new report without duplicating, which is not expected. I don't understand why this is happening.

Anyone have any ideas?

Community
  • 1
  • 1
  • This might be a dupe of the first question you linked since the answer to this problem is in the comments of the second question http://stackoverflow.com/a/5976910/3806046. Not going to flag though because I have no idea – jkeuhlen Aug 12 '15 at 19:24
  • I don't think so, the solution was more specific than just duplicating the child. – Brian Sekelsky Aug 12 '15 at 19:35

2 Answers2

3

I believe the guilty is the following line

copy.responses = self.responses.dup

The returned value from self.responses is an ActiveRecord::Relation. When you call dup you are duplicating the relation instance, not the resources pointed by the scope.

If you want to duplicate the response objects you need to first load them.

copy.responses = self.responses.map { |response| response.dup }

or

copy.responses = self.responses.map(&:dup)
Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
0

dup does a shallow copy. It does not copy all of its child objects as well. This is important to note with arrays and hashes as well.

The solution is to essentially write a clone method for your model:

def clone(new_period)
  copy = self.class.new self.attributes.slice(*%w{attributes to copy})
  copy.report_id = Report.maximum(:report_id).next
  copy.period_id = new_period
  copy.responses = Response.clone_multiple(self.responses)
  copy.save
end

Likewise with the response, add a class method to clone a collection:

class << self
  def clone_multiple(collection)
    collection.map do |response|
      copy = self.new(response.attributes.slice(*%w{attributes to clone})
      copy.save
      copy
    end
  end
end
agmcleod
  • 13,321
  • 13
  • 57
  • 96