在 rails 中克隆记录,是否可以克隆关联和深度复制?

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

提问人:CafeHey 提问时间:5/12/2011 最后编辑:benzadoCafeHey 更新时间:11/27/2018 访问量:30582

问:

我正在 .clone -ing rails 中的记录......

  new_blerg = Blerg.find(1).clone

此记录具有大量关联,这些关联甚至具有关联。

有没有办法深度复制记录并克隆它,以便它也与所有这些关联一起克隆?

Ruby-on-Rails ActiveRecord 克隆 深层复制

评论

0赞 idmean 9/18/2023
这回答了你的问题吗?Ruby on Rails 对象及其属性的深度复制/深度克隆

答:

23赞 Max Williams 5/12/2011 #1

您需要编写自己的 clone_with_associations 方法,该方法会通过一组特定的关联列出。从理论上讲,你可以编写一些使用reflect_on_all_associations的通用的东西,但你需要对关联的对象做同样的事情,这将不可避免地最终创建一个循环,产生无限数量的记录。

所以,就写你自己的吧。类似的东西

  #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

现在你可以做

@new_blerg = Blerg.find(1).clone_with_associations

评论

25赞 RocketR 10/26/2011
你会得到一个破碎的原始物体,因为这会窃取你的联想。您还需要克隆它们。new_blerg.foos = self.foos
1赞 hellion 5/17/2014
这是最好的答案。以我的拙见,在这种情况下自己滚动,比使用宝石更干净、更容易、更不神奇。
0赞 Max Williams 5/19/2014
RocketR - 好点子。我想我假设 .foos 关系是“拥有并属于许多人”,在这种情况下会很好,但如果 foo belongs_to blerg 那么是的,它会改变相关的 foos。
0赞 Ardian 11/19/2016
谢谢@MaxWilliams!我使用了这种方法。
0赞 lucas 7/24/2019
为什么每次约会后都要保存?你能在最后保存吗?
33赞 Vaughn Draughon 2/29/2012 #2

您可能会很好地利用 ActiveRecord 3.2 的 Amoeba gem

它支持简单和自动的递归复制、关联、字段预处理和高度灵活和强大的配置 DSL,既可以应用于模型,也可以动态应用。has_onehas_manyhas_and_belongs_to_many

请务必查看 Amoeba 文档,但使用非常简单......

gem install amoeba

或添加

gem 'amoeba'

到您的 Gemfile

然后将 Amoeba 模块添加到模型中,并像往常一样运行该方法dup

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

您的新帖子应该具有最初与之关联的所有标签,并且所有评论也应该重复。你可以通过 DSL 禁用各种记录的复制,你可以在文档中读到,但例如,如果你想保留标签,而不是注释,你可以做这样的事情:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    include_field :comments
  end
end

或使用独占语法

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    exclude_field :comments
  end
end

或者通过指定要识别(并因此复制)的字段类型

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    recognize :has_and_belongs_to_many
  end
end

这些不同的选项中的每一个都应该导致使用与旧帖子相同的标签重新关联新帖子,但不会重复评论。

如果您启用它们,阿米巴也会自动递归到子记录中

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

您还可以在字段前面添加一些额外的数据,以指示唯一性

class Post < ActiveRecord::Base
  has_many :comments

  amoeba do
    enable
    prepend :title => "Copy of "
  end
end

除了预置之外,您还可以在给定字段上追加或运行正则表达式

享受!:)

评论

2赞 Amala 4/12/2012
哇,这颗宝石真是太神奇了。我不得不推出自己的复制系统,但它不起作用,但您的宝石运行得非常好。
3赞 kibaekr 1/31/2014
示例中的 .dup 是否应该像文档中定义的那样是“new_post = my_post.amoeba_dup”?
6赞 Sam 3/31/2016
@kibaekr从README历史记录中,我发现“截至2012年12月11日,Amoeba不再覆盖内置方法,而是实现了自己的方法,称为...”ActiveRecord::Base#dupamoeba_dup
20赞 Rob 6/13/2012 #3

同样,这个宝石似乎工作得很好:https://github.com/moiristo/deep_cloneable,而且非常易于使用。

gem ‘deep_cloneable’, ‘~> 1.4.0’

然后:

pirate.deep_clone :include => :mateys

评论

3赞 Benjineer 3/1/2016
我发现这比 - 模型中不需要声明更容易实现。amoeba
1赞 bwest87 3/3/2016
是的,这比 Ameoba 的开销要小得多,无需学习声明或 DSL。我认为它也更“滑稽”。灵活性也很稳固。Rails 应该将其添加为 AR 方法。
1赞 Paul Danelli 3/14/2017
新宠宝石。