如何用deep_cloneable gem正确克隆ActiveRecord对象A及其关联B、C、D?
解决方案
完全可以用deep_cloneable实现你要的完整克隆效果,关键得处理好D对象同时关联新B和新C的逻辑,之前的写法问题出在没同步替换D的双归属关联。
推荐写法(利用memoize映射)
deep_cloneable自带memoize功能,能自动记录原对象和克隆对象的对应关系,刚好可以用来处理D的双关联替换:
original_a = A.first new_a = original_a.deep_clone( include: [:bs, :cs, :ds], memoize: true ) do |original, clone| # 给克隆的B、C记录原id(可选,方便后续追溯,不需要可以删掉) clone.original_id = original.id if [B, C].include?(original.class) # 处理D对象,替换成克隆后的B和C if original.is_a?(D) clone.b = memo[original.b] clone.c = memo[original.c] end end new_a.save
为啥之前的写法不行?
- 第一种写法
include: [ {bs: :ds}, :cs ]:克隆Ds的时候只处理了B的关联替换,C还是用原来的旧对象,因为Ds是跟着B走的,没同步替换C的关联。 - 第二种写法
include: [ {bs: :ds}, { cs: :ds} ]:等于从B和C两个路径分别克隆Ds,结果生成了两组重复的D,而且关联的是交叉的旧对象(新B配旧C、新C配旧B)。
另一种手动处理的写法
如果不想用memoize,也可以手动匹配克隆后的B和C来关联D:
original_a = A.first new_a = original_a.deep_clone(include: [:bs, :cs]) # 给每个克隆的B匹配对应的D,并关联到新的C new_a.bs.each do |cloned_b| original_b = B.find(cloned_b.original_id) # 这里需要克隆B时保留原id original_b.ds.each do |original_d| # 找到对应克隆后的C(假设克隆C时也保留了原id) cloned_c = new_a.cs.find { |c| c.original_id == original_d.c_id } # 克隆D并关联新B和新C cloned_b.ds << original_d.deep_clone.tap do |new_d| new_d.b_id = cloned_b.id new_d.c_id = cloned_c.id end end end new_a.save
内容的提问来源于stack exchange,提问作者Dan SimBed
相关产品推荐
相关产品推荐

