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


411

我想制作一个activerecord记录的副本,在流程中更改单个字段(除了id之外)。最简单的方法是什么?

我意识到我可以创建一个新记录,然后遍历每个字段,逐字段复制数据-但我认为必须有一种更简单的方法来执行此操作...

如:

 @newrecord=Record.copy(:id)  *perhaps?*

Answers:


622

要获取副本,请使用clone(或dup for rails 3.1+)方法:

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

然后,您可以更改所需的任何字段。

ActiveRecord会覆盖内置的Object#clone,从而为您提供一个具有未分配ID的新记录(未保存到DB)。
请注意,它不会复制关联,因此如果需要,您将必须手动执行此操作。

Rails 3.1 clone是一个浅表副本,请改用dup ...


6
在Rails 3.1.0.beta中仍然可以使用吗?当我这样做q = p.clone,然后p == q,我true回来。另一方面,如果使用q = p.dup,则false在比较它们时会返回。
Autumnsault 2011年

1
克隆上Rails 3.1文档说它仍然可以使用,但是我使用的是Rails 3.1.0.rc4,甚至该new?方法也不起作用。
图拉格2011年

12
看来此功能已被dup取代:gist.github.com/994614
skattyadz,2011年

74
绝对不要使用克隆。正如其他张贴者所提到的那样,clone方法现在委托使用Kernel#clone来复制ID。从现在开始使用ActiveRecord :: Base#dup
bradgonesurfing 2011年

5
我不得不说,这真是痛苦。如果您没有良好的规格覆盖范围,则对预期功能进行的此类简单更改可能会削弱某些重要功能。
马特·史密斯

74

根据您的需求和编程风格,您还可以结合使用类的新方法和合并。由于缺少更好的简单示例,假设您已将任务安排在某个日期,并且您希望将其复制到另一个日期。任务的实际属性并不重要,因此:

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 2012年

当我执行此操作时,由于has_many关系导致存在一列,因此一列出现未知属性错误。有没有办法解决?
鲁宾·马丁内斯(Ruben Martinez)

2
@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-这可能吗?
BKSpurgeon '16

1
不幸的是,old_task.attribtes也分配ID字段。它不是为我工作
BKSpurgeon

32

您可能还喜欢ActiveRecord 3.2 的变形虫

在你的情况,你可能想使用的nullifyregexprefix在配置DSL可用的选项。

它支持has_onehas_many和的轻松自动递归复制has_and_belongs_to_many协会,现场预处理和既能对模型和动态应用了灵活而强大的配置DSL。

一定要检查变形虫文档,但用法非常简单...

只是

gem install amoeba

或添加

gem 'amoeba'

到您的Gemfile

然后将变形虫块添加到模型中,并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

关联的递归复制很容易,也只需在子模型上启用变形虫

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还有更多选项,因此请务必查看文档。

请享用!:)


好答案。谢谢你的细节!
Derek 2012年

谢谢工作!但是我有一个问题,在保存克隆的对象之前,如何在克隆中添加新条目?
Mohd Anas

1
只是在这里解决。正确的方法.amoeba_dup不只是.dup。我正在尝试执行此代码,但是在这里不起作用。
维克多


24

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

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

当我执行此操作时,unknown attribute由于has_many关系导致存在一列,因此出现一列错误。有没有办法解决?
鲁宾·马丁内斯(Ruben Martinez)

与rails4,它不记录创建一个唯一的ID

4
要使用Rails 4创建新记录,请执行User.create(old_user.attributes.merge({ login: "newlogin", id: nil }))。这将以正确的唯一ID保存新用户。
RajeshM 2015年

Rails具有Hash#exceptHash#slice,可能使建议的方法功能更强大且更不易出错。无需添加其他库,易于扩展。
kucaahbe


4

在Rails 5中,您可以像这样简单地创建重复的对象或记录。

new_user = old_user.dup

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     

2

这是重写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版本


0

您还可以检查act_as_inheritable gem。

“作为继承的行为是专门为Rails / ActiveRecord模型编写的Ruby Gem。它旨在与Self-Referential Association或具有共享可继承属性的父级的模型一起使用。这将使您继承任何属性或与父模型的关系。”

通过添加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">

希望这可以帮到你。


0

由于可能存在更多逻辑,因此在复制模型时,我建议创建一个新类,在其中处理所有需要的逻辑。为了缓解这种情况,有一个宝石可以帮助您:clowne

根据他们的文档示例,对于用户模型:

class User < ActiveRecord::Base
  # create_table :users do |t|
  #  t.string :login
  #  t.string :email
  #  t.timestamps null: false
  # end

  has_one :profile
  has_many :posts
end

您创建克隆器类:

class UserCloner < Clowne::Cloner
  adapter :active_record

  include_association :profile, clone_with: SpecialProfileCloner
  include_association :posts

  nullify :login

  # params here is an arbitrary Hash passed into cloner
  finalize do |_source, record, params|
    record.email = params[:email]
  end
end

class SpecialProfileCloner < Clowne::Cloner
  adapter :active_record

  nullify :name
end

然后使用它:

user = User.last
#=> <#User(login: 'clown', email: 'clown@circus.example.com')>

cloned = UserCloner.call(user, email: 'fake@example.com')
cloned.persisted?
# => false

cloned.save!
cloned.login
# => nil
cloned.email
# => "fake@example.com"

# associations:
cloned.posts.count == user.posts.count
# => true
cloned.profile.name
# => nil

从项目中复制了示例,但是它将清晰地说明您可以实现的目标。

为了快速而简单地记录,我将选择:

Model.new(Model.last.attributes.reject {|k,_v| k.to_s == 'id'}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.