我想制作一个activerecord记录的副本,更改过程中的单个字段(除了id之外)。 最简单的方法是什么?
我意识到我可以创建一个新记录,然后遍历每个字段,逐字段复制数据-但我认为必须有一种更简单的方法来执行此操作...
如:
1
| @newrecord=Record.copy(:id) *perhaps?* |
要获得副本,请使用clone(对于rails则为dup)3.1:
1 2 3 4 5
| # 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 ...
根据您的需求和编程风格,您还可以结合使用类的新方法和合并。由于缺少更好的简单示例,假设您已将任务安排在某个日期,并且您想将其复制到另一个日期。任务的实际属性并不重要,因此:
1 2
| 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。
和平。
您可能还喜欢ActiveRecord 3.2的变形虫。
在您的情况下,您可能想利用配置DSL中可用的nullify,regex或prefix选项。
它支持has_one,has_many和has_and_belongs_to_many关联的轻松自动递归复制,字段预处理以及可应用于模型和即??时应用的高度灵活且功能强大的配置DSL。
一定要检查变形虫文档,但用法非常简单...
只是
或添加
到您的Gemfile
然后将变形虫块添加到模型中,并照常运行dup方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 |
您还可以控制通过多种方式复制哪些字段,但是,例如,如果要防止重复注释,但又想保留相同的标签,则可以执行以下操作:
1 2 3 4 5 6 7 8
| class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
exclude_field :comments
end
end |
您还可以预处理字段,以帮助使用前缀和后缀以及正则表达式来指示唯一性。此外,还有许多选项,因此您可以根据自己的目的以最易读的方式编写:
1 2 3 4 5 6 7 8 9 10 11
| 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 |
关联的递归复制很容易,也只需在子模型上启用变形虫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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还有更多选项,因此请务必查看文档。
请享用! :)
如果您不想复制ID,请使用ActiveRecord :: Base#dup
我通常只是复制属性,更改我需要更改的内容:
1
| new_user = User.new(old_user.attributes.merge(:login =>"newlogin")) |
如果您需要具有关联的深层副本,建议使用deep_cloneable gem。
在Rails 5中,您可以像这样简单地创建重复的对象或记录。
简单的方法是:
1 2 3 4 5 6 7
| #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 |
要么
1 2 3 4 5 6
| # if your rails < 3.1
o = Model.find(id)
(1..109).each do |item|
new_record = o.clone
new_record.save
end |
这是重写ActiveRecord #dup方法的示例,以自定义实例复制并包括关系复制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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版本
由于可能存在更多逻辑,因此在复制模型时,我建议创建一个新类,在其中处理所有需要的逻辑。
为了缓解这种情况,有一个宝石可以帮助您:clowne
根据他们的文档示例,对于用户模型:
1 2 3 4 5 6 7 8 9 10
| 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 |
您创建克隆器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 |
然后使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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'}
您还可以检查act_as_inheritable gem。
"作为继承的行为是专门为Rails / ActiveRecord模型编写的Ruby Gem。它旨在与自引用协会或具有共享可继承属性的父级的模型一起使用。这将使您继承任何属性或与父模型的关系。"
通过在模型中添加acts_as_inheritable,您将可以使用以下方法:
Inherit_attributes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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"> |
希望这可以帮到你。