วิธีที่ง่ายที่สุดในการทำซ้ำบันทึกสถิติของ Activerecord คืออะไร


412

ฉันต้องการทำสำเนาบันทึก activerecord โดยเปลี่ยนฟิลด์เดียวในกระบวนการ (นอกเหนือจากid ) วิธีที่ง่ายที่สุดในการบรรลุเป้าหมายนี้คืออะไร?

ฉันรู้ว่าฉันสามารถสร้างระเบียนใหม่จากนั้นวนซ้ำแต่ละเขตข้อมูลคัดลอกเขตข้อมูลแต่ละเขตข้อมูล - แต่ฉันคิดว่าต้องมีวิธีที่ง่ายกว่าในการทำเช่นนี้ ...

เช่น:

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

คำตอบ:


622

ในการรับสำเนาให้ใช้วิธีการโคลน (หรือทำซ้ำสำหรับราง 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

จากนั้นคุณสามารถเปลี่ยนฟิลด์ใดก็ได้ที่คุณต้องการ

ActiveRecord จะเขียนทับ Object # ในตัวเพื่อให้คุณบันทึกใหม่ (ไม่ได้บันทึกลงในฐานข้อมูล) ด้วยรหัสที่ไม่ได้กำหนด
โปรดทราบว่ามันไม่ได้คัดลอกการเชื่อมโยงดังนั้นคุณจะต้องทำด้วยตนเองหากคุณต้องการ

Rails 3.1 clone เป็นสำเนาตื้นใช้ dup แทน ...


6
สิ่งนี้ยังคงใช้งานได้กับ Rails 3.1.0.beta หรือไม่? เมื่อฉันทำq = p.cloneแล้วp == qฉันจะtrueกลับมา ในทางกลับกันถ้าฉันใช้q = p.dupฉันจะfalseกลับมาเมื่อเปรียบเทียบพวกเขา
Autumnsault

1
Rails 3.1 เอกสารในโคลนบอกว่ามันยังคงทำงานได้ แต่ฉันใช้ Rails 3.1.0.rc4 และแม้กระทั่งnew?วิธีการที่ไม่ได้ทำงาน
Turadg

12
ดูเหมือนว่าฟังก์ชันนี้ถูกแทนที่ด้วย dup: gist.github.com/994614
skattyadz

74
ไม่ใช้โคลนแน่นอน ดังที่ผู้โพสต์คนอื่น ๆ ได้กล่าวถึงวิธีการโคลนตอนนี้มอบหมายให้ใช้ Kernel # clone ซึ่งจะคัดลอกรหัส ใช้ ActiveRecord :: Base # dup ต่อจากนี้ไป
bradgonesurfing

5
ฉันต้องบอกว่านี่เป็นความเจ็บปวดที่แท้จริง การเปลี่ยนแปลงอย่างง่ายเช่นนี้กับฟังก์ชั่นที่ตั้งใจสามารถทำลายคุณสมบัติที่สำคัญบางอย่างหากคุณไม่มีการครอบคลุมที่ดี
Matt Smith

74

ขึ้นอยู่กับความต้องการและรูปแบบการเขียนโปรแกรมของคุณคุณยังสามารถใช้การรวมกันของวิธีการใหม่ของชั้นเรียนและการผสาน สำหรับการขาดตัวอย่างง่ายๆที่ดีกว่าสมมติว่าคุณมีงานที่กำหนดไว้สำหรับวันที่แน่นอนและคุณต้องการทำซ้ำกับวันที่อื่น คุณสมบัติที่แท้จริงของงานนั้นไม่สำคัญดังนั้น:

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

จะสร้างงานใหม่ด้วย:id => nil, :scheduled_on => some_new_dateและคุณลักษณะอื่น ๆ ทั้งหมดเช่นเดียวกับงานต้นฉบับ ใช้ Task.new คุณจะต้องเรียกบันทึกอย่างชัดเจนดังนั้นหากคุณต้องการให้บันทึกโดยอัตโนมัติเปลี่ยน Task.new เป็น Task.create

สันติภาพ.


5
ไม่แน่ใจว่าวิธีการที่ดีของความคิดนี้เป็น B / C คุณจะได้รับWARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atกลับมา
bcackerman

เมื่อฉันทำสิ่งนี้ฉันได้รับข้อผิดพลาดแอตทริบิวต์ที่ไม่รู้จักกับหนึ่งคอลัมน์เนื่องจากคอลัมน์ที่อยู่ที่นั่นเนื่องจากมีความสัมพันธ์แบบ has_many มีวิธีแก้ไขไหม?
Ruben Martinez Jr.

2
@RubenMartineJr ฉันรู้ว่านี่เป็นโพสต์เก่า แต่ใช่คุณสามารถแก้ไขได้โดยใช้ '.except' บนแอตทริบิวต์ hash: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) => some_new_date}))
Ninigi

@PhillipKoebbe ขอบคุณ - แต่ถ้าฉันต้องการให้ id ไม่เป็นโมฆะ? ฉันต้องการให้ Rails กำหนดรหัสใหม่โดยอัตโนมัติเมื่อฉันสร้างซ้ำ - เป็นไปได้หรือไม่
BKSpurgeon

1
old_task.attribtes กำหนดเขตข้อมูล ID ด้วยเช่นกันโชคไม่ดี มันใช้งานไม่ได้สำหรับฉัน
BKSpurgeon

32

คุณอาจชอบอัญมณี Amoebaสำหรับ ActiveRecord 3.2

ในกรณีของคุณคุณอาจต้องการที่จะทำให้การใช้งานของnullify, regexหรือprefixตัวเลือกที่มีอยู่ใน DSL การกำหนดค่า

รองรับการทำซ้ำแบบเรียกซ้ำได้ง่ายและอัตโนมัติhas_oneรวมhas_manyถึงการhas_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

คุณยังสามารถประมวลผลฟิลด์ล่วงหน้าเพื่อช่วยระบุความไม่ซ้ำกับทั้งคำนำหน้าและคำต่อท้ายเช่นเดียวกับ regexes นอกจากนี้ยังมีตัวเลือกมากมายเพื่อให้คุณสามารถเขียนในรูปแบบที่อ่านง่ายที่สุดสำหรับวัตถุประสงค์ของคุณ:

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 ก่อน

ขอบคุณมันใช้งานได้ !! แต่ฉันมีคำถามหนึ่งข้อฉันจะเพิ่มรายการใหม่ด้วยการโคลนก่อนบันทึกวัตถุที่โคลนได้อย่างไร
Mohd Anas

1
เพียงแก้ไขที่นี่ วิธีที่ถูกต้องคือไม่เพียง.amoeba_dup .dupฉันพยายามรันโค้ดนี้ แต่มันไม่ทำงานที่นี่
Victor


24

ฉันมักจะคัดลอกคุณลักษณะการเปลี่ยนแปลงสิ่งที่ฉันต้องการเปลี่ยนแปลง:

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

เมื่อฉันทำเช่นนี้ฉันได้รับunknown attributeข้อผิดพลาดกับหนึ่งคอลัมน์เนื่องจากคอลัมน์ที่มีเนื่องจากความสัมพันธ์ has_many มีวิธีแก้ไขไหม?
Ruben Martinez Jr.

ด้วย Rails 4 มันไม่ได้สร้างรหัสเฉพาะสำหรับบันทึก
Ben

4
ในการสร้างสถิติใหม่ด้วยกับทางรถไฟ 4 User.create(old_user.attributes.merge({ login: "newlogin", id: nil }))ทำ การดำเนินการนี้จะบันทึกผู้ใช้ใหม่ด้วยรหัสเฉพาะที่ถูกต้อง
RajeshM

Rails มีHash # ยกเว้นและHash # sliceซึ่งอาจทำให้วิธีที่แนะนำมีประสิทธิภาพมากที่สุดและมีข้อผิดพลาดน้อยกว่า ไม่จำเป็นต้องเพิ่ม libs เพิ่มเติมง่ายต่อการขยาย
kucaahbe

10

หากคุณต้องการสำเนาลึกที่มีการเชื่อมโยงฉันแนะนำอัญมณีDeep_cloneable


ฉันด้วย. ฉันลองใช้อัญมณีนี้และใช้งานได้ครั้งแรกใช้งานง่ายมาก
Rob


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

นี่คือตัวอย่างของการแทนที่#dupเมธอดActiveRecord เพื่อกำหนดค่าการทำซ้ำอินสแตนซ์และรวมการทำซ้ำความสัมพันธ์ด้วย:

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

หมายเหตุ: วิธีนี้ไม่ต้องการอัญมณีภายนอกใด ๆ แต่ต้องใช้รุ่น ActiveRecord ที่ใหม่กว่าพร้อม#dupวิธีการที่ใช้


0

นอกจากนี้คุณยังสามารถตรวจสอบอัญมณีactions_as_inheritable

"การกระทำที่สืบทอดเป็นอัญมณีทับทิมที่เขียนขึ้นโดยเฉพาะสำหรับโมเดล Rails / ActiveRecord ซึ่งมีวัตถุประสงค์เพื่อใช้กับ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

เนื่องจากอาจมีตรรกะมากขึ้นเมื่อทำซ้ำแบบจำลองฉันขอแนะนำให้สร้างคลาสใหม่ที่คุณจัดการกับตรรกะที่จำเป็นทั้งหมด เพื่อความสะดวกนั้นมีอัญมณีที่สามารถช่วยได้: ตัวตลก

ตามตัวอย่างเอกสารประกอบสำหรับรุ่นผู้ใช้:

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

คุณสร้างคลาส cloner ของคุณ:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.