Was ist der einfachste Weg, um einen Aktivaufzeichnungsdatensatz zu duplizieren?

411

Ich möchte eine Kopie eines Aktivaufzeichnungsdatensatzes erstellen und dabei ein einzelnes Feld ändern (zusätzlich zur ID ). Was ist der einfachste Weg, um dies zu erreichen?

Mir ist klar, dass ich einen neuen Datensatz erstellen und dann jedes der Felder durchlaufen kann, die die Daten Feld für Feld kopieren - aber ich dachte, es muss einen einfacheren Weg geben, dies zu tun ...

sowie:

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

Antworten:

622

Verwenden Sie die Methode clone (oder dup for Rails 3.1+), um eine Kopie zu erhalten:

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Dann können Sie die gewünschten Felder ändern.

ActiveRecord überschreibt den integrierten Object # -Klon , um Ihnen einen neuen (nicht in der Datenbank gespeicherten) Datensatz mit einer nicht zugewiesenen ID zu geben.
Beachten Sie, dass keine Assoziationen kopiert werden. Sie müssen dies daher manuell tun, wenn dies erforderlich ist.

Der Rails 3.1-Klon ist eine flache Kopie. Verwenden Sie stattdessen dup ...

Michael Sepcot
quelle
6
Funktioniert das noch in Rails 3.1.0.beta? Wenn ich das tue q = p.cloneund dann p == qkomme ich truezurück. Auf der anderen Seite, wenn ich benutze q = p.dup, bekomme ich falsezurück , wenn ich sie vergleiche.
Autumnsault
1
Die Rails 3.1-Dokumente auf dem Klon sagen, dass es immer noch funktioniert, aber ich verwende Rails 3.1.0.rc4 und selbst die new?Methode funktioniert nicht.
Turadg
12
Es sieht so aus, als ob diese Funktionalität durch dup
skattyadz
74
Verwenden Sie auf keinen Fall einen Klon. Wie andere Poster bereits erwähnt haben, delegiert die Klonmethode jetzt die Verwendung des Kernel # -Klons, der die ID kopiert. Verwenden Sie ab sofort
ActiveRecord
5
Ich muss sagen, das war ein echter Schmerz. Eine einfache Änderung der beabsichtigten Funktionalität wie diese könnte einige wichtige Funktionen beeinträchtigen, wenn Sie keine gute Spezifikationsabdeckung haben.
Matt Smith
74

Abhängig von Ihren Anforderungen und Ihrem Programmierstil können Sie auch eine Kombination aus der neuen Methode der Klasse und dem Zusammenführen verwenden. In Ermangelung eines besseren einfaches Beispiel an , dass Sie eine Aufgabe für einen bestimmten Zeitpunkt geplant haben , und Sie wollen , dass es auf einen anderen Termin duplizieren. Die tatsächlichen Attribute der Aufgabe sind nicht wichtig, also:

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

wird eine neue Aufgabe mit erstellen :id => nil, :scheduled_on => some_new_dateund alle anderen Attribute der gleiche wie der ursprüngliche Aufgabe. Wenn Sie Task.new verwenden, müssen Sie save explizit aufrufen. Wenn Sie also möchten, dass es automatisch gespeichert wird, ändern Sie Task.new in Task.create.

Frieden.

Phillip Koebbe
quelle
5
Nicht ganz sicher , wie gut die Idee , dies ist b / c Sie erhalten WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atzurückgekehrt
bcackerman
Wenn ich dies tue, erhalte ich einen unbekannten Attributfehler mit einer Spalte aufgrund einer Spalte, die aufgrund einer has_many-Beziehung vorhanden ist. Gibt es einen Weg daran vorbei?
Ruben Martinez Jr.
2
@ RubenMartineJr. Ich weiß, dass dies ein alter Beitrag ist, aber ja, Sie können dies umgehen, indem Sie '.except' für die Attribute hash verwenden: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want ,: another_aydw) .merge ({: Scheduled_on) => some_new_date}))
Ninigi
@PhillipKoebbe danke - aber was ist, wenn ich möchte, dass die ID nicht null ist? Ich möchte, dass Rails beim Erstellen des Duplikats automatisch eine neue ID zuweisen. Ist dies möglich?
BKSpurgeon
1
old_task.attribtes weist leider auch das ID-Feld zu. Es funktioniert nicht für mich
BKSpurgeon
32

Möglicherweise gefällt Ihnen auch das Amöben-Juwel für ActiveRecord 3.2.

In Ihrem Fall möchten Sie wahrscheinlich die Verwendung der machen nullify, regexoder prefixOptionen , die in der Konfiguration DSL.

Es unterstützt die einfache und automatische rekursive Vervielfältigung has_one, has_manyund has_and_belongs_to_manyVereinigungen, Feld Vorverarbeitung und eine hochflexible und leistungsstarke Konfiguration DSL , die sowohl auf das Modell angewandt werden können und im laufenden Betrieb .

Schauen Sie sich unbedingt die Amöben-Dokumentation an, aber die Verwendung ist ziemlich einfach ...

gerade

gem install amoeba

oder hinzufügen

gem 'amoeba'

zu deiner Gemfile

Fügen Sie dann den Amöbenblock zu Ihrem Modell hinzu und führen Sie die dupMethode wie gewohnt aus

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

Sie können auch steuern, welche Felder auf verschiedene Weise kopiert werden. Wenn Sie jedoch verhindern möchten, dass Kommentare dupliziert werden, aber dieselben Tags beibehalten möchten, können Sie Folgendes tun:

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

  amoeba do
    exclude_field :comments
  end
end

Sie können Felder auch vorverarbeiten, um die Eindeutigkeit sowohl mit Präfixen und Suffixen als auch mit regulären Ausdrücken anzuzeigen. Darüber hinaus gibt es zahlreiche Optionen, mit denen Sie für Ihren Zweck am besten lesbar schreiben können:

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

Das rekursive Kopieren von Assoziationen ist einfach. Aktivieren Sie Amöben auch für untergeordnete Modelle

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

Die Konfiguration DSL bietet noch mehr Optionen. Lesen Sie daher unbedingt die Dokumentation.

Genießen! :) :)

Vaughn Draughon
quelle
Gute Antwort. Danke für das Detail!
Derek Prior
Danke es funktioniert !! Aber ich habe eine Frage, wie ich beim Klonen neue Einträge hinzufügen kann, bevor ich das geklonte Objekt speichere.
Mohd Anas
1
Nur eine Lösung hier. Die richtige Methode ist .amoeba_dupnicht nur .dup. Ich habe versucht, diesen Code auszuführen, aber hier hat er nicht funktioniert.
Victor
31

Verwenden Sie ActiveRecord :: Base # dup, wenn Sie die ID nicht kopieren möchten

Bradgonesurfing
quelle
1
@Thorin gemäß der oben akzeptierten Antwort, es sieht so aus, als ob die richtige Methode für Rails <3.1 ist.clone
Dan Weaver
24

Normalerweise kopiere ich einfach die Attribute und ändere alles, was ich ändern muss:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
François Beausoleil
quelle
Wenn ich das mache, erhalte ich einen unknown attributeFehler mit einer Spalte aufgrund einer Spalte, die aufgrund einer has_many-Beziehung vorhanden ist. Gibt es einen Weg daran vorbei?
Ruben Martinez Jr.
Mit Rails4 wird keine eindeutige ID für den Datensatz erstellt
Ben
4
Führen Sie Folgendes aus, um mit Rails 4 einen neuen Datensatz zu erstellen User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Dadurch wird ein neuer Benutzer mit der richtigen eindeutigen ID gespeichert.
RajeshM
Rails hat Hash # außer und Hash # Slice , was die vorgeschlagene Methode möglicherweise am leistungsfähigsten und weniger fehleranfällig macht. Keine Notwendigkeit, zusätzliche Bibliotheken hinzuzufügen, einfach zu erweitern.
Kucaahbe
10

Wenn Sie eine tiefe Kopie mit Assoziationen benötigen, empfehle ich das Juwel deep_cloneable .

Raidfive
quelle
Ich auch. Ich habe dieses Juwel ausprobiert und es hat beim ersten Mal funktioniert, sehr einfach zu bedienen.
Rob
4

In Rails 5 können Sie einfach ein doppeltes Objekt oder einen Datensatz wie diesen erstellen.

new_user = old_user.dup
Foram Thakral
quelle
2

Der einfache Weg ist:

#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

Oder

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

Hier ist ein Beispiel für das Überschreiben der ActiveRecord- #dupMethode zum Anpassen der Instanzduplizierung und zum Einbeziehen der Beziehungsduplizierung:

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

Hinweis: Für diese Methode ist kein externes Juwel erforderlich, es ist jedoch eine neuere ActiveRecord-Version mit #dupimplementierter Methode erforderlich

Zoran Majstorovic
quelle
0

Sie können auch das Juwel Acts_as_inheritable überprüfen .

"Acts As Inheritable" ist ein Ruby Gem, der speziell für Rails / ActiveRecord-Modelle geschrieben wurde. Es ist für die Verwendung mit der Self-Referential Association oder für ein Modell mit einem übergeordneten Element vorgesehen, das die vererbbaren Attribute gemeinsam nutzt . Auf diese Weise können Sie jedes Attribut oder erben Beziehung aus dem Elternmodell. "

Durch Hinzufügen acts_as_inheritablezu Ihren Modellen haben Sie Zugriff auf folgende Methoden:

erben_attribute

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

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

Hoffe das kann dir helfen.

Esbanarango
quelle
0

Da es beim Duplizieren eines Modells mehr Logik geben könnte, würde ich vorschlagen, eine neue Klasse zu erstellen, in der Sie die gesamte erforderliche Logik verarbeiten. Um das zu erleichtern, gibt es ein Juwel, das helfen kann: Clowne

Gemäß den Dokumentationsbeispielen für ein Benutzermodell:

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

Sie erstellen Ihre Cloner-Klasse:

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

und dann benutze es:

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

Beispiel aus dem Projekt kopiert, aber es gibt eine klare Vorstellung davon, was Sie erreichen können.

Für eine schnelle und einfache Aufzeichnung würde ich gehen mit:

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

Paulo Fidalgo
quelle