Qual è il modo più semplice per duplicare un record di activerecord?


412

Voglio fare una copia di un record di activerecord, modificando un singolo campo nel processo (oltre all'ID ). Qual è il modo più semplice per raggiungere questo obiettivo?

Mi rendo conto che potrei creare un nuovo record e quindi scorrere su ciascuno dei campi copiando i dati campo per campo - ma ho pensato che ci potesse essere un modo più semplice per farlo ...

ad esempio:

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

Risposte:


622

Per ottenere una copia, utilizzare il metodo clone (o dup for rails 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Quindi puoi cambiare qualunque campo tu voglia.

ActiveRecord sovrascrive il clone Object # incorporato per fornire un nuovo record (non salvato nel DB) con un ID non assegnato.
Nota che non copia le associazioni, quindi dovrai farlo manualmente se necessario.

Il clone di Rails 3.1 è una copia superficiale, usa invece dup ...


6
Funziona ancora in Rails 3.1.0.beta? Quando lo faccio q = p.clonee poi p == qtorno trueindietro. D'altra parte, se uso q = p.dup, falsetorno a confrontarli.
Autumnsault,

1
I documenti di Rails 3.1 sul clone dicono che funziona ancora, ma sto usando Rails 3.1.0.rc4 e anche il new?metodo non funziona.
Turadg,

12
Sembra che questa funzionalità sia stata sostituita con dup: gist.github.com/994614
skattyadz,

74
Sicuramente NON usare il clone. Come altri poster hanno menzionato, il metodo clone ora delega all'utilizzo del clone Kernel # che copierà l'id. Usa ActiveRecord :: Base # dup da ora in poi
bradgonesurfing il

5
Devo dire che è stato un vero dolore. Una semplice modifica come questa alla funzionalità prevista potrebbe paralizzare alcune funzionalità importanti se non si disponeva di una buona copertura delle specifiche.
Matt Smith

74

A seconda delle esigenze e dello stile di programmazione, puoi anche utilizzare una combinazione del nuovo metodo della classe e unire. Per mancanza di un semplice esempio migliore , supponi di avere un'attività pianificata per una certa data e desideri duplicarla in un'altra data. Gli attributi effettivi dell'attività non sono importanti, quindi:

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

creerà una nuova attività con :id => nil, :scheduled_on => some_new_datee tutti gli altri attributi uguali all'attività originale. Usando Task.new, dovrai chiamare esplicitamente save, quindi se vuoi che venga salvato automaticamente, cambia Task.new in Task.create.

Pace.


5
Non sono sicuro di quanto sia buona l'idea che questo sia b / c che vieni WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atrestituito
bcackerman il

Quando lo faccio, ricevo un errore di attributo sconosciuto con una colonna a causa di una colonna presente a causa di una relazione has_many. C'è un modo per aggirare questo?
Ruben Martinez Jr.,

2
@RubenMartineJr. So che questo è un vecchio post, ma sì, puoi aggirare questo problema usando '.except' sull'hash degli attributi: new_task = Task.new (old_task.attributes.except (: attrib_you_dont_want,: another_aydw) .merge ({: pianificato_on => some_new_date}))
Ninigi

@PhillipKoebbe grazie - ma cosa succede se voglio che l'id non sia nullo? Voglio che le rotaie assegnino automaticamente un nuovo ID quando creo il duplicato - è possibile?
BKSpurgeon,

1
purtroppo old_task.attribtes assegna anche il campo ID. Non funziona per me
BKSpurgeon,

32

Potrebbe piacerti anche la gemma Amoeba per ActiveRecord 3.2.

Nel tuo caso, probabilmente si vuole fare uso dei nullify, regexo prefixopzioni disponibili nel DSL configurazione.

Supporta semplice e automatico la duplicazione ricorsiva di has_one, has_manye has_and_belongs_to_manyassociazioni, campo di pre-elaborazione e un modem DSL di configurazione altamente flessibile e potente che può essere applicata sia al modello e al volo.

assicurati di consultare la documentazione Amoeba ma l'utilizzo è piuttosto semplice ...

appena

gem install amoeba

o aggiungi

gem 'amoeba'

al tuo Gemfile

quindi aggiungi il blocco ameba al tuo modello ed esegui il dupmetodo come al solito

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

Puoi anche controllare quali campi vengono copiati in molti modi, ma ad esempio, se volevi impedire la duplicazione dei commenti ma volevi mantenere gli stessi tag, potresti fare qualcosa del genere:

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

  amoeba do
    exclude_field :comments
  end
end

È inoltre possibile preelaborare i campi per aiutare a indicare l'univocità con prefissi e suffissi nonché regex. Inoltre, ci sono anche numerose opzioni in modo da poter scrivere nello stile più leggibile per il tuo scopo:

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

La copia ricorsiva delle associazioni è semplice, basta abilitare l'ameba anche sui modelli figlio

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

Il DSL di configurazione ha ancora più opzioni, quindi assicurati di consultare la documentazione.

Godere! :)


Bella risposta. Grazie per il dettaglio!
Derek prima del

Grazie funziona !! Ma ho una domanda come posso aggiungere nuove voci con la clonazione prima di salvare l'oggetto clonato?
Mohd Anas,

1
Solo una soluzione qui. Il metodo corretto è .amoeba_dup, non solo .dup. Stavo cercando di eseguire questo codice, ma non funzionava qui.
Victor,


24

Di solito copio solo gli attributi, cambiando qualunque cosa io abbia bisogno di cambiare:

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

Quando lo faccio, ricevo un unknown attributeerrore con una colonna a causa di una colonna che è lì a causa di una relazione has_many. C'è un modo per aggirare questo?
Ruben Martinez Jr.,

con rails4, non crea un ID univoco per il record
Ben

4
Per creare un nuovo record con con Rails 4, fare User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Ciò salverà un nuovo utente con l'id univoco corretto.
RajeshM,

Rails ha Hash # tranne e Hash # slice , rendendo potenzialmente il metodo suggerito più potente e meno soggetto a errori. Non è necessario aggiungere ulteriori librerie, facile da estendere.
Kucaahbe,

10

Se hai bisogno di una copia profonda con le associazioni, ti consiglio la gemma deep_cloneable .


Anch'io. Ho provato questo gioiello e ha funzionato per la prima volta, molto facile da usare.
Rob,

4

In Rails 5 puoi semplicemente creare oggetti duplicati o registrare in questo modo.

new_user = old_user.dup

2

Il modo più semplice è:

#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

O

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

2

Ecco un esempio del #dupmetodo ActiveRecord di sostituzione per personalizzare la duplicazione dell'istanza e includere anche la duplicazione delle relazioni:

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

Nota: questo metodo non richiede alcun gem esterno ma richiede una versione di ActiveRecord più recente con il #dupmetodo implementato


0

Puoi anche controllare la gemma act_as_inheritable .

"Acts As Inheritable è una gemma di Ruby scritta appositamente per i modelli Rails / ActiveRecord. È pensata per essere utilizzata con l' Associazione autoreferenziale o con un modello con un genitore che condivide gli attributi ereditabili. Ciò ti consentirà di ereditare qualsiasi attributo o relazione dal modello principale ".

Aggiungendo acts_as_inheritableai tuoi modelli avrai accesso a questi metodi:

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

Spero che questo possa aiutarti.


0

Dato che potrebbe esserci più logica, durante la duplicazione di un modello, suggerirei di creare una nuova classe, in cui gestisci tutta la logica necessaria. Per facilitare ciò, c'è una gemma che può aiutare: pagliaccio

Secondo i loro esempi di documentazione, per un modello utente:

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

Crea la tua classe di clonazione:

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

e poi usalo:

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

Esempio copiato dal progetto, ma darà una visione chiara di ciò che puoi ottenere.

Per una registrazione semplice e veloce andrei con:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.