最简单的复制activerecord记录的方法是什么?

472

我想复制一个ActiveRecord对象,在此过程中更改单个字段(除了id)。 实现这一点的最简单方法是什么?

我知道我可以创建一个新记录,然后迭代每个字段逐个复制数据,但我想知道是否有更简单的方法。

也许像这样:

 new_record = Record.copy(:id)
12个回答

705

要获取一个副本,请使用 dup 方法(对于 < rails 3.1+ 版本,请使用 clone 方法):

#rails >= 3.1
new_record = old_record.dup

# rails < 3.1
new_record = old_record.clone

然后,您可以更改任何您想要的字段。

ActiveRecord覆盖了内置的Object#clone方法,以给您一个新的(未保存到数据库)记录并分配一个未赋值的ID。
请注意,它不会复制关联,所以如果需要,您必须手动完成这个过程。

Rails 3.1中的clone是浅层复制,请改用dup...


6
在Rails 3.1.0.beta版本中,这个还能用吗?当我执行q = p.clone,然后比较p == q时,返回的是true。但是,如果我使用q = p.dup,则在比较它们时返回false。 - Translunar
12
看起来这个功能已经被dup替换了:https://gist.github.com/994614 - skattyadz
83
绝对不要使用“clone”。正如其他帖子中提到的,现在克隆方法委托给使用Kernel#clone的方法,这将复制ID。从现在开始,请改用ActiveRecord :: Base#dup。 - bradgonesurfing
5
我必须说,这真的让人很头疼。如果没有很好的规格覆盖范围,像这样对预期功能进行简单更改可能会瘫痪一些重要功能。 - Matt Smith
5
如果您想更改特定属性,可以在使用dupclone时添加tap方法,例如:clone = record.dup.tap { |new_clone| new_clone.name = "dup_#{new_clone.name}" }。这将允许您修改复制的对象的特定属性,而不会改变其它属性或行为。 - Pablo Cantero
显示剩余5条评论

79

根据您的需求和编程风格,您还可以使用类的新方法和合并的组合。举个简单的例子,假设您有一个计划在某个日期执行的任务,并且您想将其复制到另一个日期。任务的实际属性并不重要,所以:

old_task = Task.find(task_id)
new_task = Task.new(old_task.attributes.merge({:scheduled_on => some_new_date}))

将创建一个新任务,其中:id => nil:scheduled_on => some_new_date,而其他所有属性与原始任务相同。使用Task.new,您将必须显式调用save,因此如果想要自动保存,可以将Task.new更改为Task.create。

祝好!


5
不太确定这是一个好主意,因为你会收到WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at的返回信息。 - bcackerman
当我这样做时,由于一个由has_many关系引起的列存在,我会得到一个未知属性错误,其中包含一个列。有没有什么解决方法? - Ruben Martinez Jr.
3
@RubenMartineJr. 我知道这是一篇旧帖子,但你可以通过在属性哈希上使用 .except 来避免这个问题: new_task = Task.new(old_task.attributes.except(:attribute_you_dont_want, :another_aydw).merge({:scheduled_on => some_new_date})) - Ninigi
@PhillipKoebbe 谢谢 - 但是如果我不想让id为空怎么办?我希望Rails在创建副本时自动分配一个新的id - 这可能吗? - BenKoshy
2
old_task.attributes也会分配ID字段,这很不幸。对我来说不起作用。 - BenKoshy

35
您可能也会喜欢针对 ActiveRecord 3.2 的 Amoeba gem
在您的情况下,您可能想要利用配置 DSL 中提供的 nullifyregexprefix 选项。
它支持易于使用和自动递归复制 has_onehas_manyhas_and_belongs_to_many 关联、字段预处理以及高度灵活和强大的配置 DSL,可同时应用于模型和即时操作。
请务必查看Amoeba Documentation,但使用非常简单...
只需
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

您还可以以多种方式控制要复制的字段,但例如,如果您想防止复制评论但仍想保留相同的标签,则可以执行以下操作:
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
    include_field :tags
    prepend :title => "Copy of "
    append :contents => " (copied version)"
    regex :contents => {:replace => /dog/, :with => "cat"}
  end
end

递归复制关联非常容易,只需在子模型上启用Amoeba即可。

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

配置DSL还有更多选项,请务必查看文档。
享受吧! :)

谢谢,它有效了!但我有一个问题,如何在保存克隆对象之前使用克隆添加新条目? - Mohd Anas
2
这里只需要修复一下。正确的方法是 .amoeba_dup,而不仅仅是 .dup。我试图在这里执行这段代码,但它没有起作用。 - Victor

33

1
@Thorin 根据上面被接受的答案,看起来在 Rails < 3.1 中正确的方法是 .clone - Dan Weaver

27

通常我只是复制属性,改变我需要更改的内容:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))

当我这样做时,由于一个由has_many关系引起的列,我会得到一个“未知属性”错误,其中包含一个列。有没有什么解决方法? - Ruben Martinez Jr.
使用Rails4时,它不会为记录创建唯一的ID。 - Ben
5
若要使用Rails 4创建新记录,请执行User.create(old_user.attributes.merge({ login: "newlogin", id: nil }))。这将保存一个带有正确唯一标识符的新用户。 - RajeshM
Rails拥有Hash#exceptHash#slice,这使得建议的方法更加强大且不易出错。无需添加额外的库,易于扩展。 - kucaahbe

10

如果您需要带有关联的深拷贝,我推荐使用deep_cloneable gem。


我也是。我尝试了这个宝石,第一次就成功了,非常容易使用。 - Rob

6
在Rails 5中,您可以像这样简单地创建重复对象或记录。
new_user = old_user.dup

3

以下是一个覆盖 ActiveRecord #dup 方法以自定义实例复制并包含关联复制的示例:

class Offer < ApplicationRecord
  has_many :offer_items

  def dup
    super.tap do |new_offer|

      # change title of the new instance
      new_offer.title = "Copy of #{@offer.title}"

      # duplicate offer_items as well
      self.offer_items.each { |offer_item| new_offer.offer_items << offer_item.dup }
    end
  end
end

注意:这种方法不需要任何外部 gem,但需要实现 #dup 方法的新版 ActiveRecord。

2
最简单的方式是:
#your rails >= 3.1 (i was done it with Rails 5.0.0.1)
  o = Model.find(id)
 # (Range).each do |item|
 (1..109).each do |item|
   new_record = o.dup
   new_record.save
 end

或者
# if your rails < 3.1
 o = Model.find(id)
 (1..109).each do |item|
   new_record = o.clone
   new_record.save
 end     

0

您还可以检查acts_as_inheritable gem。

"Acts As Inheritable是一个专门为Rails/ActiveRecord模型编写的Ruby Gem。它旨在与自引用关联一起使用,或者与具有共享可继承属性的父模型一起使用。这将使您从父模型继承任何属性或关系。"

通过向您的模型添加acts_as_inheritable,您将可以访问以下方法:

inherit_attributes

class Person < ActiveRecord::Base

  acts_as_inheritable attributes: %w(favorite_color last_name soccer_team)

  # Associations
  belongs_to  :parent, class_name: 'Person'
  has_many    :children, class_name: 'Person', foreign_key: :parent_id
end

parent = Person.create(last_name: 'Arango', soccer_team: 'Verdolaga', favorite_color:'Green')

son = Person.create(parent: parent)
son.inherit_attributes
son.last_name # => Arango
son.soccer_team # => Verdolaga
son.favorite_color # => Green

inherit_relations

class Person < ActiveRecord::Base

  acts_as_inheritable associations: %w(pet)

  # Associations
  has_one     :pet
end

parent = Person.create(last_name: 'Arango')
parent_pet = Pet.create(person: parent, name: 'Mango', breed:'Golden Retriver')
parent_pet.inspect #=> #<Pet id: 1, person_id: 1, name: "Mango", breed: "Golden Retriver">

son = Person.create(parent: parent)
son.inherit_relations
son.pet.inspect # => #<Pet id: 2, person_id: 2, name: "Mango", breed: "Golden Retriver">

希望这可以帮助你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接