我想复制一个 ActiveRecord 对象,更改过程中的单个字段(除了 id)。完成此任务的最简单方法是什么?
我意识到我可以创建一个新记录,然后遍历每个字段,逐个字段地复制数据 - 但我认为必须有一种更简单的方法来做到这一点。
也许是这样的:
new_record = Record.copy(:id)
要获取副本,请使用 dup(或克隆 < rails 3.1+)方法:
#rails >= 3.1
new_record = old_record.dup
# rails < 3.1
new_record = old_record.clone
然后,您可以更改所需的任何字段。
ActiveRecord overrides the built-in Object#clone 为您提供具有未分配 ID 的新(未保存到数据库)记录。
请注意,它不会复制关联,因此如果需要,您必须手动执行此操作。
Rails 3.1 clone is a shallow copy, use dup instead...
根据您的需要和编程风格,您还可以使用类的新方法和合并的组合。由于缺少更好的简单示例,假设您有一个任务安排在某个日期,并且您想将它复制到另一个日期。任务的实际属性并不重要,因此:
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。
和平。
WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at
返回
您可能还喜欢 ActiveRecord 3.2 的 Amoeba gem。
在您的情况下,您可能希望使用配置 DSL 中可用的 nullify
、regex
或 prefix
选项。
它支持 has_one
、has_many
和 has_and_belongs_to_many
关联的简单自动递归复制、字段预处理和高度灵活且功能强大的配置 DSL,可同时应用于模型和动态。
请务必查看 Amoeba Documentation,但使用起来非常简单...
只是
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 有更多选项,因此请务必查看文档。
享受! :)
.amoeba_dup
,而不仅仅是 .dup
。我试图执行这段代码,但它在这里不起作用。
我通常只是复制属性,更改我需要更改的任何内容:
new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
unknown attribute
错误。有没有办法解决?
User.create(old_user.attributes.merge({ login: "newlogin", id: nil }))
。这将保存具有正确唯一 ID 的新用户。
在 Rails 5 中,您可以像这样简单地创建重复的对象或记录。
new_user = old_user.dup
这是一个覆盖 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
方法
简单的方法是:
#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
您还可以检查 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">
希望这可以帮到你。
由于可能存在更多逻辑,因此在复制模型时,我建议创建一个新类,您可以在其中处理所有需要的逻辑。为了缓解这种情况,有一个可以提供帮助的 gem: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'}
试试 rails 的 dup
方法:
new_record = old_record.dup.save
q = p.clone
,然后执行p == q
时,我会返回true
。另一方面,如果我使用q = p.dup
,我会在比较它们时返回false
。dup
或clone
的附加功能是使用tap
例如clone = record.dup.tap { |new_clone| new_clone.name = "dup_#{new_clone.name}" }