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?
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.
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.
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! :)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With