Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cloning a record in rails, is it possible to clone associations and deep copy?

I'm .clone -ing a record in rails...

  new_blerg = Blerg.find(1).clone 

This record has loads and loads of associations, and those associations even have associations.

Is there a way to deep-copy a record and clone it so it is cloned with all of those associations too?

like image 596
MintDeparture Avatar asked May 12 '11 10:05

MintDeparture


People also ask

How do you duplicate a record in rails?

You generally use #clone if you want to copy an object including its internal state. This is what Rails is using with its #dup method on ActiveRecord. It uses #dup to allow you to duplicate a record without its "internal" state (id and timestamps), and leaves #clone up to Ruby to implement.

What is DUP in Ruby?

According to Ruby-doc, both #clone and #dup can be used to create a shallow copy of an object, which only traverse one layer of complexity, meaning that the instance variables of obj are copied, but not the objects they reference. They would all share the same attributes; modifying one would result a change on another.


2 Answers

You may get some good use out of the Amoeba gem for ActiveRecord 3.2.

It supports easy and automatic recursive duplication of has_one, has_many and has_and_belongs_to_many associations, field preprocessing and a highly flexible and powerful configuration DSL that can be applied both to the model and on the fly.

be sure to check out the Amoeba Documentation but usage is pretty easy...

just

gem install amoeba 

or add

gem 'amoeba' 

to your Gemfile

then add the amoeba block to your model and run the dup method as usual

class Post < ActiveRecord::Base   has_many :comments   has_and_belongs_to_many :tags    amoeba do     enable   end end  class Comment < ActiveRecord::Base   belongs_to :post end  class Tag < ActiveRecord::Base   has_and_belongs_to_many :posts end  class PostsController < ActionController   def some_method     my_post = Post.find(params[:id])     new_post = my_post.dup     new_post.save   end end 

Your new post should have all the tags that were originally associated with it, and all the comments should be duplicated as well. You can disable the duplication of various records through the DSL, which you can read about in the documentation, but for example, if you wanted to keep the tags, but not the comments you could do something like this:

class Post < ActiveRecord::Base   has_many :comments   has_and_belongs_to_many :tags    amoeba do     include_field :comments   end end 

or using the exclusive syntax

class Post < ActiveRecord::Base   has_many :comments   has_and_belongs_to_many :tags    amoeba do     exclude_field :comments   end end 

or by specifying which field types to recognize (and thusly copy)

class Post < ActiveRecord::Base   has_many :comments   has_and_belongs_to_many :tags    amoeba do     recognize :has_and_belongs_to_many   end end 

each of these various options should result in re-associating the new post with the same tags as the old post, but without duplicating the comments.

Amoeba will also automatically recurse in to child records if you enable them

class Post < ActiveRecord::Base   has_many :comments    amoeba do     enable   end end  class Comment < ActiveRecord::Base   belongs_to :post   has_many :ratings    amoeba do     enable   end end  class Rating < ActiveRecord::Base   belongs_to :comment end 

You can also prefix fields with some extra data to indicate uniqueness

class Post < ActiveRecord::Base   has_many :comments    amoeba do     enable     prepend :title => "Copy of "   end end 

and in addition to prepend you can also append to or run a regex on a given field

Enjoy! :)

like image 71
Vaughn Draughon Avatar answered Sep 20 '22 21:09

Vaughn Draughon


You'd need to write your own clone_with_associations method which goes through a specific listed set of associations. Theoretically you could write something generic which uses reflect_on_all_associations but you would need to do the same on the associated objects, and this would inevitably end up creating a loop that generates an infinite amount of records.

So, just write your own. Something like

  #in Blerg   has_many :foos   has_many :bars #bars also have many chickens which we want to copy over as well   def clone_with_associations     new_blerg = self.dup     new_blerg.save     #simple association     new_blerg.foos = self.foos     #two-level association      self.bars.each do |bar|       new_bar = bar.clone       new_bar.save       new_bar.chickens = bar.chickens        new_blerg.bars << bar     end     new_blerg   end 

Now you can do

@new_blerg = Blerg.find(1).clone_with_associations 
like image 21
Max Williams Avatar answered Sep 22 '22 21:09

Max Williams