moiristo / deep_cloneable

This gem gives every ActiveRecord::Base object the possibility to do a deep clone that includes user specified associations.
MIT License
785 stars 89 forks source link

Is it possible to clone has_many through associations ? #116

Closed webmix closed 4 years ago

webmix commented 4 years ago

Hello

I would like to clone a model (Site) and all its associations and nested associations (Page, ItemGroup).

It is working fine unless for a has_many through association : Page has_many :item_groups, :through => :page_item_groups

For example : I got those associations

+---------+---------------+
| page_id | item_group_id |
+---------+---------------+
|       1 |             1 |
|       3 |             2 |
|       4 |             3 |
+---------+---------------+

Doing a clone using cloned_site = site.deep_clone include: [ :pages, { item_groups: :items, pages: :item_groups } ], I got this :

+---------+---------------+
| page_id | item_group_id |
+---------+---------------+
|       1 |             1 |
|       3 |             2 |
|       4 |             3 |
|       1 |             4 |
|       3 |             5 |
|       4 |             6 |
+---------+---------------+

page_id keep the old reference, and is not getting updated with the cloned one.

I also tried many variants, I either ends with accurate item_group_id or accurate page_id but I never had the correct association with only new cloned records.

is it an expected behaviour ? or maybe am I doing something wrong ?

moiristo commented 4 years ago

Has many through is always a bit tricky yes. When you want to dup the item groups as well, I think the solution is to go through the join association instead, something like:

cloned_site = site.deep_clone include: { pages: { page_item_groups: { item_group: :items } } }
webmix commented 4 years ago

Hi @moiristo Thanks for the fast answer !

Interesting, it is almost working, the association with has_many through seems to be correct now ! However, the "cloned item_groups" are now getting associated with the original record and not the clone.

I will keep digging this nested syntax and see if I can find a solution

webmix commented 4 years ago

I could finally fix the issue, my associations are a bit particular, since ItemGroup belongs to Site and has many Page, and Page also belongs to Site

So I am using

cloned_site = site.deep_clone include: [ pages: { page_item_groups: { item_group: :items } } ]

which works fine for nested associations (Page, ItemGroup, Item) but fails to associate Site and ItemGroup.

So I am associating them manually like this :

      cloned_site.pages.each do |page|
        page.item_groups.each do |item_group|
          item_group.site_id = cloned_site.id
          item_group.save!
        end
      end

Not sure it's the best solution, but it works fine like this !

NDuggan commented 4 years ago

@webmix thanks for your comment, I initially tried your way as I couldn't get the 2nd association (ItemGroup in your example) to link to the original (Site) as well as doing the other side of the nested associations.

But after hitting some other issues I re-read the docs and believe this is what the dictionary option is for. I've set use_dictionary: true and now I don't have to loop over the nested associations to set the site_id manually.