Apa cara termudah untuk menduplikasi catatan activerecord?

412

Saya ingin membuat salinan catatan activerecord, mengubah satu bidang dalam proses (selain id ). Apa cara paling sederhana untuk mencapai ini?

Saya menyadari bahwa saya dapat membuat catatan baru, dan kemudian mengulangi masing-masing bidang menyalin data bidang demi bidang - tetapi saya pikir pasti ada cara yang lebih mudah untuk melakukan ini ...

seperti:

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

Jawaban:

622

Untuk mendapatkan salinan, gunakan metode klon (atau dup untuk rel 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Kemudian Anda dapat mengubah bidang mana saja yang Anda inginkan.

ActiveRecord menimpa klon Objek # bawaan untuk memberi Anda catatan baru (tidak disimpan ke DB) dengan ID yang belum ditetapkan.
Perhatikan bahwa ini tidak menyalin asosiasi, jadi Anda harus melakukan ini secara manual jika perlu.

Rails 3.1 clone adalah salinan dangkal, gunakan dup sebagai gantinya ...

Michael Sepcot
sumber
6
Apakah ini masih berfungsi di Rails 3.1.0.beta? Ketika saya melakukannya q = p.clone, dan kemudian p == q, saya truekembali. Di sisi lain, jika saya gunakan q = p.dup, saya falsekembali ketika membandingkannya.
Autumnsault
1
Dokumen Rails 3.1 pada klon mengatakan itu masih berfungsi, tapi saya menggunakan Rails 3.1.0.rc4 dan bahkan new?metode ini tidak berfungsi.
Turadg
12
Sepertinya fungsi ini telah diganti dengan dup: gist.github.com/994614
skattyadz
74
Jelas JANGAN menggunakan klon. Seperti poster lain yang menyebutkan metode klon sekarang delegasi untuk menggunakan Kernel # clone yang akan menyalin id. Gunakan ActiveRecord :: Base # dup mulai sekarang
bradgonesurfing
5
Saya harus mengatakan, ini adalah rasa sakit yang nyata. Perubahan sederhana seperti ini ke fungsionalitas yang diinginkan dapat melumpuhkan beberapa fitur penting jika Anda tidak memiliki cakupan spesifikasi yang baik.
Matt Smith
74

Bergantung pada kebutuhan dan gaya pemrograman Anda, Anda juga dapat menggunakan kombinasi metode baru kelas dan penggabungan. Karena tidak ada contoh sederhana yang lebih baik , misalkan Anda memiliki tugas yang dijadwalkan untuk tanggal tertentu dan Anda ingin menduplikasinya ke tanggal lain. Atribut sebenarnya dari tugas tidak penting, jadi:

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

akan membuat tugas baru dengan :id => nil, :scheduled_on => some_new_date, dan semua atribut lainnya sama dengan tugas aslinya. Dengan menggunakan Task.new, Anda harus memanggil save secara eksplisit, jadi jika Anda ingin menyimpannya secara otomatis, ubah Task.new menjadi Task.create.

Perdamaian.

Phillip Koebbe
sumber
5
Tidak yakin seberapa bagus ide ini jika Anda dapat WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atkembali
bcackerman
Ketika saya melakukan ini, saya mendapatkan kesalahan atribut yang tidak diketahui dengan satu kolom karena ada kolom yang ada karena hubungan has_many. Apakah ada jalan keluarnya?
Ruben Martinez Jr.
2
@RubenMartineJr. Saya tahu ini adalah posting lama, tapi ya Anda bisa menyiasatinya dengan menggunakan '.except' pada atribut hash: new_task = Task.new (old_task.attributes.except (: atribut_you_dont_want,: another_aydw) .merge ({: dijadwalkan_on => some_new_date}))
Ninigi
@PhillipKoebbe terima kasih - tetapi bagaimana jika saya ingin id tidak menjadi nol? Saya ingin rel untuk secara otomatis menetapkan id baru ketika saya membuat duplikat - apakah ini mungkin?
BKSpurgeon
1
old_task.attribtes juga memberikan bidang ID. Ini tidak berfungsi untuk saya
BKSpurgeon
32

Anda mungkin juga menyukai permata Amoeba untuk ActiveRecord 3.2.

Dalam kasus Anda, Anda mungkin ingin menggunakan nullify, regexatau prefixopsi yang tersedia di DSL konfigurasi.

Ini mendukung duplikasi rekursif mudah dan otomatis has_one, has_manydan has_and_belongs_to_manyasosiasi, preprocessing lapangan dan DSL konfigurasi yang sangat fleksibel dan kuat yang dapat diterapkan baik untuk model dan on the fly.

pastikan untuk memeriksa Dokumentasi Amuba tetapi penggunaannya cukup mudah ...

hanya

gem install amoeba

atau tambahkan

gem 'amoeba'

ke Gemfile Anda

kemudian tambahkan blok amuba ke model Anda dan jalankan dupmetode seperti biasa

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

Anda juga dapat mengontrol bidang mana yang disalin dengan berbagai cara, tetapi misalnya, jika Anda ingin mencegah komentar agar tidak digandakan tetapi Anda ingin mempertahankan tag yang sama, Anda bisa melakukan sesuatu seperti ini:

class Post < ActiveRecord::Base
  has_many :comments
  has_and_belongs_to_many :tags

  amoeba do
    exclude_field :comments
  end
end

Anda juga bisa preprocess bidang untuk membantu menunjukkan keunikan dengan awalan dan sufiks serta regex. Selain itu, ada juga banyak opsi sehingga Anda dapat menulis dengan gaya yang paling mudah dibaca untuk tujuan Anda:

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

Menyalin asosiasi secara rekursif itu mudah, cukup aktifkan amuba pada model anak juga

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

Konfigurasi DSL memiliki lebih banyak opsi, jadi pastikan untuk memeriksa dokumentasi.

Nikmati! :)

Vaughn Draughon
sumber
Jawaban yang bagus Terima kasih untuk detailnya!
Derek Prior
Terima kasih itu berhasil !! Tetapi saya punya satu pertanyaan bagaimana cara menambahkan entri baru dengan kloning sebelum menyimpan objek yang dikloning?
Mohd Anas
1
Perbaiki saja di sini. Metode yang benar adalah .amoeba_dup, bukan hanya .dup. Saya mencoba menjalankan kode ini, tetapi tidak berfungsi di sini.
Victor
31

Gunakan ActiveRecord :: Base # dup jika Anda tidak ingin menyalin id

bradgonesurfing
sumber
1
@ Titin sesuai jawaban yang diterima di atas, sepertinya metode yang benar untuk Rails <3.1 is.clone
Dan Weaver
24

Saya biasanya hanya menyalin atribut, mengubah apa pun yang perlu saya ubah:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
François Beausoleil
sumber
Ketika saya melakukan ini, saya mendapatkan unknown attributekesalahan dengan satu kolom karena ada kolom yang ada karena hubungan has_many. Apakah ada jalan keluarnya?
Ruben Martinez Jr.
dengan rails4, itu tidak membuat id unik untuk catatan
Ben
4
Untuk membuat catatan baru dengan dengan Rails 4, lakukan User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Ini akan menghemat pengguna baru dengan id unik yang benar.
RajeshM
Rails memiliki Hash # kecuali dan Hash # slice , berpotensi membuat metode yang disarankan paling kuat dan lebih rentan kesalahan. Tidak perlu menambahkan lib tambahan, mudah diperpanjang.
kucaahbe
10

Jika Anda membutuhkan salinan yang dalam dengan asosiasi, saya sarankan permata deep_cloneable .

raidfive
sumber
Saya juga. Saya mencoba permata ini dan berhasil pertama kali, sangat mudah digunakan.
Rob
4

Di Rails 5 Anda cukup membuat objek duplikat atau merekam seperti ini.

new_user = old_user.dup
Foram Thakral
sumber
2

Cara yang mudah adalah:

#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

Atau

# if your rails < 3.1
 o = Model.find(id)
 (1..109).each do |item|
   new_record = o.clone
   new_record.save
 end     
ThienSuBS
sumber
2

Berikut adalah contoh #dupmetode ActiveRecord yang mengesampingkan untuk mengkustomisasi duplikasi instance dan juga menyertakan duplikasi relasi:

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

Catatan: metode ini tidak memerlukan permata eksternal tetapi membutuhkan versi ActiveRecord yang lebih baru dengan #dupmetode yang diterapkan

Zoran Majstorovic
sumber
0

Anda juga dapat memeriksa permata act_as_inheritable .

"Acts As Inheritable adalah Ruby Gem yang secara khusus ditulis untuk model Rails / ActiveRecord. Ini dimaksudkan untuk digunakan dengan Self-Referential Association , atau dengan model yang memiliki orang tua yang memiliki atribut bawaan. Ini akan memungkinkan Anda mewarisi atribut apa pun atau hubungan dari model induk. "

Dengan menambahkan acts_as_inheritableke model Anda, Anda akan memiliki akses ke metode ini:

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

Semoga ini bisa membantu Anda.

esbanarango
sumber
0

Karena mungkin ada lebih banyak logika, ketika menduplikasi model, saya akan menyarankan untuk membuat kelas baru, di mana Anda menangani semua logika yang diperlukan. Untuk memudahkannya, ada permata yang bisa membantu: clowne

Sesuai contoh dokumentasi mereka, untuk model Pengguna:

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

Anda membuat kelas cloner Anda:

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

dan kemudian menggunakannya:

user = User.last
#=> <#User(login: 'clown', email: '[email protected]')>

cloned = UserCloner.call(user, email: '[email protected]')
cloned.persisted?
# => false

cloned.save!
cloned.login
# => nil
cloned.email
# => "[email protected]"

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

Contoh disalin dari proyek, tetapi akan memberikan visi yang jelas tentang apa yang dapat Anda capai.

Untuk catatan cepat dan sederhana saya akan pergi dengan:

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

Paulo Fidalgo
sumber