444

我想复制一个 ActiveRecord 对象,更改过程中的单个字段(除了id)。完成此任务的最简单方法是什么?

我意识到我可以创建一个新记录,然后遍历每个字段,逐个字段地复制数据 - 但我认为必须有一种更简单的方法来做到这一点。

也许是这样的:

 new_record = Record.copy(:id)
4

12 回答 12

667

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

#rails >= 3.1
new_record = old_record.dup

# rails < 3.1
new_record = old_record.clone

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

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

Rails 3.1 克隆是浅拷贝,请使用 dup 代替...

于 2008-09-12T21:56:44.727 回答
75

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

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。

和平。

于 2008-09-15T13:48:34.157 回答
35

您可能还喜欢 ActiveRecord 3.2 的Amoeba gem

在您的情况下,您可能希望使用配置 DSL 中可用的nullifyregexprefix选项。

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

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

只是

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

享受!:)

于 2012-02-28T16:12:44.453 回答
32

如果您不想复制 id,请使用ActiveRecord::Base#dup

于 2011-08-05T13:59:20.313 回答
25

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

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
于 2008-09-15T13:36:15.340 回答
10

如果您需要带有关联的深层副本,我推荐deep_cloneable gem。

于 2011-09-01T02:22:49.767 回答
6

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

new_user = old_user.dup
于 2019-01-04T08:56:28.900 回答
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,但需要更新的 ActiveRecord 版本并#dup实现方法

于 2018-08-20T07:04:13.343 回答
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     
于 2016-10-30T13:58:00.290 回答
0

您还可以检查acts_as_inheritable gem。

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

通过添加acts_as_inheritable到您的模型中,您将可以访问这些方法:

继承属性

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

继承关系

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">

希望这可以帮到你。

于 2015-10-27T22:27:24.553 回答
0

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

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

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'}

于 2018-03-16T14:19:17.963 回答
0

试试rails的dup方法:

new_record = old_record.dup.save
于 2021-04-04T17:21:36.080 回答