Cách dễ nhất để sao chép một bản ghi Activerecord là gì?


412

Tôi muốn tạo một bản sao của một bản ghi Activerecord, thay đổi một trường duy nhất trong quy trình (ngoài id ). Cách đơn giản nhất để thực hiện điều này là gì?

Tôi nhận ra rằng tôi có thể tạo một bản ghi mới, và sau đó lặp lại qua từng trường sao chép trường dữ liệu theo trường - nhưng tôi nghĩ rằng phải có một cách dễ dàng hơn để làm điều này ...

nhu la:

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

Câu trả lời:


622

Để có được một bản sao, sử dụng phương thức clone (hoặc dup cho rails 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Sau đó, bạn có thể thay đổi bất kỳ lĩnh vực nào bạn muốn.

ActiveRecord ghi đè lên bản sao Object # tích hợp để cung cấp cho bạn bản ghi mới (chưa được lưu vào DB) với ID chưa được gán.
Lưu ý rằng nó không sao chép các hiệp hội, vì vậy bạn sẽ phải làm điều này bằng tay nếu bạn cần.

Bản sao Rails 3.1 là bản sao nông, sử dụng bản sao thay thế ...


6
Điều này vẫn hoạt động trong Rails 3.1.0.beta? Khi tôi làm q = p.clone, và sau đó p == q, tôi truetrở lại. Mặt khác, nếu tôi sử dụng q = p.dup, tôi sẽ falsequay lại khi so sánh chúng.
Autumnsault

1
Các tài liệu Rails 3.1 trên bản sao cho biết nó vẫn hoạt động, nhưng tôi đang sử dụng Rails 3.1.0.rc4 và thậm chí new?phương thức này không hoạt động.
Turadg

12
Có vẻ như chức năng này đã được thay thế bằng dup: gist.github.com/994614
skattyadz

74
Chắc chắn KHÔNG sử dụng bản sao. Như các áp phích khác đã đề cập đến phương thức nhân bản, bây giờ các đại biểu sử dụng Kernel # clone sẽ sao chép id. Sử dụng ActiveRecord :: Base # dup từ bây giờ
bradgonesurfing

5
Tôi phải nói rằng, đây là một nỗi đau thực sự. Một thay đổi đơn giản như thế này đối với chức năng dự định có thể làm tê liệt một số tính năng quan trọng nếu bạn không có phạm vi bảo hiểm tốt.
Matt Smith

74

Tùy thuộc vào nhu cầu và phong cách lập trình của bạn, bạn cũng có thể sử dụng kết hợp phương thức mới của lớp và hợp nhất. Vì thiếu một ví dụ đơn giản tốt hơn , giả sử bạn có một nhiệm vụ được lên lịch cho một ngày nhất định và bạn muốn sao chép nó sang một ngày khác. Các thuộc tính thực tế của nhiệm vụ không quan trọng, vì vậy:

old_task = Task.find (task_id)
new_task = Task.new (old_task.attribut.merge ({: lên lịch_on => some_new_date}))

sẽ tạo ra một nhiệm vụ mới với :id => nil, :scheduled_on => some_new_datevà tất cả các thuộc tính khác giống như nhiệm vụ ban đầu. Sử dụng Task.new, bạn sẽ phải gọi lưu một cách rõ ràng, vì vậy nếu bạn muốn nó được lưu tự động, hãy thay đổi Task.new thành Task.create.

Sự thanh bình.


5
Không hoàn toàn chắc chắn ý tưởng này tốt như thế nào khi bạn được WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_attrả lại
bcackerman

Khi tôi làm điều này, tôi nhận được một lỗi thuộc tính không xác định với một cột vì một cột có do mối quan hệ has_many. Có cách nào để khắc phục điều này?
Ruben Martinez Jr.

2
@RubenMartineJr. Tôi biết đây là một bài viết cũ, nhưng vâng, bạn có thể khắc phục điều này bằng cách sử dụng '.except' trên các thuộc tính hash: new_task = Task.new (old_task.attribut.except (: property_you_dont_want ,: Another_aydw) .merge ({: lên lịch => some_new_date}))
Ninigi

@PhillipKoebbe cảm ơn bạn - nhưng nếu tôi muốn id không có giá trị thì sao? Tôi muốn đường ray tự động gán một id mới khi tôi tạo bản sao - điều này có thể không?
BKSpurgeon

1
old_task.attribtes cũng chỉ định trường ID. Nó không hoạt động với tôi
BKSpurgeon

32

Bạn cũng có thể thích đá quý Amoeba cho ActiveRecord 3.2.

Trong trường hợp của bạn, có thể bạn muốn tận dụng nullify, regexhoặc prefixtùy chọn có sẵn trong DSL cấu hình.

Nó hỗ trợ sao chép đệ quy dễ dàng và tự động has_one, has_manyvà các has_and_belongs_to_manyliên kết, tiền xử lý trường và DSL cấu hình rất linh hoạt và mạnh mẽ có thể được áp dụng cho cả mô hình và trên đường đi.

hãy chắc chắn kiểm tra Tài liệu Amoeba nhưng việc sử dụng khá dễ dàng ...

chỉ

gem install amoeba

hoặc thêm

gem 'amoeba'

tới Gemfile của bạn

sau đó thêm khối amip vào mô hình của bạn và chạy dupphương thức như bình thường

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

Bạn cũng có thể kiểm soát trường nào được sao chép theo nhiều cách, nhưng ví dụ: nếu bạn muốn ngăn bình luận bị trùng lặp nhưng bạn muốn duy trì các thẻ giống nhau, bạn có thể làm điều gì đó như sau:

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

  amoeba do
    exclude_field :comments
  end
end

Bạn cũng có thể tiền xử lý các trường để giúp chỉ ra tính duy nhất với cả tiền tố và hậu tố cũng như biểu thức chính. Ngoài ra, cũng có nhiều tùy chọn để bạn có thể viết theo phong cách dễ đọc nhất cho mục đích của mình:

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

Sao chép đệ quy các hiệp hội rất dễ dàng, chỉ cần kích hoạt amip trên các mô hình con là tốt

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 cấu hình có nhiều tùy chọn hơn, vì vậy hãy chắc chắn kiểm tra tài liệu.

Thưởng thức! :)


Câu trả lời chính xác. Cảm ơn các chi tiết!
Derek Trước

Cảm ơn nó hoạt động !! Nhưng tôi có một câu hỏi làm thế nào để tôi thêm các mục mới với nhân bản trước khi lưu đối tượng nhân bản?
Mohd Anas

1
Chỉ cần sửa chữa ở đây. Phương pháp đúng là .amoeba_dup, không chỉ .dup. Tôi đã cố gắng thực thi mã này, nhưng nó không hoạt động ở đây.
Victor


24

Tôi thường chỉ sao chép các thuộc tính, thay đổi bất cứ điều gì tôi cần thay đổi:

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

Khi tôi làm điều này, tôi gặp unknown attributelỗi với một cột vì một cột có do mối quan hệ has_many. Có cách nào để khắc phục điều này?
Ruben Martinez Jr.

với rails4, nó không tạo ra một id duy nhất cho bản ghi
Ben

4
Để tạo một bản ghi mới với Rails 4, hãy làm User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Điều này sẽ lưu một người dùng mới với id duy nhất chính xác.
RajeshM 15/03/2015

Rails có Hash # ngoại trừHash # lát , có khả năng làm cho phương thức được đề xuất mạnh nhất và ít bị lỗi hơn. Không cần thêm libs, dễ dàng mở rộng.
kucaahbe

10

Nếu bạn cần một bản sao sâu với các hiệp hội, tôi khuyên bạn nên sử dụng đá quý deep_clonizable .


Tôi cũng vậy. Tôi đã thử loại đá quý này và nó hoạt động lần đầu tiên, rất dễ sử dụng.
Cướp

4

Trong Rails 5, bạn có thể chỉ cần tạo đối tượng trùng lặp hoặc ghi lại như thế này.

new_user = old_user.dup

2

Cách dễ dàng là:

#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

Hoặc là

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

2

Dưới đây là một mẫu ghi đè #dupphương thức ActiveRecord để tùy chỉnh sao chép cá thể và bao gồm cả sao chép quan hệ:

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

Lưu ý: phương pháp này không yêu cầu bất kỳ đá quý bên ngoài nào nhưng nó yêu cầu phiên bản ActiveRecord mới hơn với #dupphương thức được triển khai


0

Bạn cũng có thể kiểm tra hành vi_as_inheritable đá quý .

"Hành vi như kế thừa là một viên đá quý Ruby được viết riêng cho các mô hình Rails / ActiveRecord. Nó có nghĩa là được sử dụng với Hiệp hội tự giới thiệu hoặc với một mô hình có cha mẹ chia sẻ các thuộc tính kế thừa. Điều này sẽ cho phép bạn thừa hưởng bất kỳ thuộc tính nào hoặc mối quan hệ từ mô hình cha mẹ. "

Bằng cách thêm acts_as_inheritable vào các mô hình của bạn, bạn sẽ có quyền truy cập vào các phương thức sau:

kế thừa

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

kế thừa_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">

Hy vọng điều này có thể giúp bạn.


0

Vì có thể có nhiều logic hơn, khi sao chép một mô hình, tôi sẽ đề nghị tạo một lớp mới, nơi bạn xử lý tất cả các logic cần thiết. Để giảm bớt điều đó, có một viên ngọc có thể giúp: chú hề

Theo ví dụ tài liệu của họ, cho một mô hình Người dùng:

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

Bạn tạo lớp cloner của bạn:

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

và sau đó sử dụng nó:

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

Ví dụ được sao chép từ dự án, nhưng nó sẽ cho một tầm nhìn rõ ràng về những gì bạn có thể đạt được.

Đối với một bản ghi nhanh chóng và đơn giản, tôi sẽ đi với:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.