¿Cuál es la forma más fácil de duplicar un registro de registro activo?

412

Quiero hacer una copia de un registro de registro activo, cambiando un solo campo en el proceso (además de la identificación ). ¿Cuál es la forma más sencilla de lograr esto?

Me doy cuenta de que podría crear un nuevo registro y luego iterar sobre cada uno de los campos copiando los datos campo por campo, pero pensé que debía haber una manera más fácil de hacerlo ...

como:

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

Respuestas:

622

Para obtener una copia, use el método de clonación (o dup para rails 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Luego puede cambiar los campos que desee.

ActiveRecord anula el clon de Object # incorporado para darle un nuevo registro (no guardado en la base de datos) con una ID sin asignar.
Tenga en cuenta que no copia las asociaciones, por lo que deberá hacerlo manualmente si es necesario.

Rails 3.1 clone es una copia superficial, use dup en su lugar ...

Michael Sepcot
fuente
66
¿Esto todavía funciona en Rails 3.1.0.beta? Cuando lo hago q = p.clone, y luego p == q, trueregreso. Por otro lado, si lo uso q = p.dup, falsevuelvo al compararlos.
Autumnsault
1
Los documentos de Rails 3.1 en clon dicen que todavía funciona, pero estoy usando Rails 3.1.0.rc4 e incluso el new?método no funciona.
Turadg
12
Parece que esta funcionalidad ha sido reemplazada por dup: gist.github.com/994614
skattyadz
74
Definitivamente NO use clon. Como otros carteles han mencionado, el método de clonación ahora delega el uso de Kernel # clone que copiará la identificación. Use ActiveRecord :: Base # dup de ahora en adelante
bradgonesurfing
55
Tengo que decir que esto fue un verdadero dolor. Un cambio simple como este a la funcionalidad prevista podría paralizar algunas características importantes si no tuviera una buena cobertura de especificaciones.
Matt Smith
74

Dependiendo de sus necesidades y estilo de programación, también puede usar una combinación del nuevo método de la clase y fusionar. A falta de un mejor ejemplo simple , suponga que tiene una tarea programada para una fecha determinada y desea duplicarla en otra fecha. Los atributos reales de la tarea no son importantes, así que:

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

creará una nueva tarea con :id => nil, :scheduled_on => some_new_datey todos los demás atributos iguales a la tarea original. Con Task.new, deberá llamar explícitamente a save, por lo que si desea guardarlo automáticamente, cambie Task.new a Task.create.

Paz.

Phillip Koebbe
fuente
55
No estoy seguro de qué tan buena idea es esto porque te WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atdevuelven
bcackerman
Cuando hago esto, obtengo un error de atributo desconocido con una columna debido a una columna que está allí debido a una relación has_many. ¿Hay alguna forma de evitar esto?
Ruben Martinez Jr.
2
@RubenMartineJr. Sé que esta es una publicación antigua, pero sí, puede solucionar esto utilizando '.except' en el hash de atributos: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) .merge ({: Scheduled_on => some_new_date}))
Ninigi
@PhillipKoebbe gracias, pero ¿qué pasa si quiero que la identificación no sea nula? Quiero que los rieles asignen automáticamente una nueva identificación cuando creo el duplicado, ¿es esto posible?
BKSpurgeon
1
old_task.attribtes también asigna el campo ID por desgracia. No está funcionando para mí
BKSpurgeon
32

También te puede interesar la gema Amoeba para ActiveRecord 3.2.

En su caso, es probable que desee hacer uso de los nullify, regexo prefixlas opciones disponibles en el DSL configuración.

Es compatible con fácil y automática la duplicación recursiva de has_one, has_manyy has_and_belongs_to_manyasociaciones, preprocesamiento de campo y una DSL configuración altamente flexible y potente que se puede aplicar tanto al modelo y sobre la marcha.

asegúrese de consultar la documentación de Amoeba, pero el uso es bastante fácil ...

sólo

gem install amoeba

o agregar

gem 'amoeba'

a tu Gemfile

luego agregue el bloque de ameba a su modelo y ejecute el dupmétodo como de costumbre

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

También puede controlar qué campos se copian de varias maneras, pero, por ejemplo, si desea evitar que los comentarios se dupliquen pero desea mantener las mismas etiquetas, puede hacer algo como esto:

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

  amoeba do
    exclude_field :comments
  end
end

También puede preprocesar campos para ayudar a indicar unicidad con prefijos y sufijos, así como expresiones regulares. Además, también hay numerosas opciones para que pueda escribir en el estilo más legible para su propósito:

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 recursiva de asociaciones es fácil, solo habilite ameba en modelos secundarios también

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

La configuración DSL tiene aún más opciones, así que asegúrese de consultar la documentación.

¡Disfrutar! :)

Vaughn Draughon
fuente
Gran respuesta. Gracias por el detalle!
Derek Prior
Gracias funciona !! Pero tengo una pregunta, ¿cómo agrego nuevas entradas con la clonación antes de guardar el objeto clonado?
Mohd Anas
1
Solo una solución aquí. El método correcto es .amoeba_dup, no solo .dup. Estaba tratando de ejecutar este código, pero no estaba funcionando aquí.
Victor
31

Use ActiveRecord :: Base # dup si no desea copiar la identificación

bradgonesurfing
fuente
1
@Thorin según la respuesta aceptada anteriormente, parece que el método correcto para Rails <3.1 es.clone
Dan Weaver
24

Usualmente solo copio los atributos, cambiando lo que sea que necesite cambiar:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
François Beausoleil
fuente
Cuando hago esto, recibo un unknown attributeerror con una columna debido a una columna que está allí debido a una relación has_many. ¿Hay alguna forma de evitar esto?
Ruben Martinez Jr.
con rails4, no crea una identificación única para el registro
Ben
44
Para crear un nuevo registro con Rails 4, hazlo User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Esto salvará a un nuevo usuario con la identificación única correcta.
RajeshM
Rails tiene Hash # except y Hash # slice , lo que potencialmente hace que el método sugerido sea más potente y menos propenso a errores. No es necesario agregar libs adicionales, fácil de extender.
kucaahbe
10

Si necesita una copia profunda con asociaciones, le recomiendo la gema deep_cloneable .

raidfive
fuente
Yo también. Probé esta gema y funcionó por primera vez, muy fácil de usar.
Rob
4

En Rails 5 simplemente puede crear objetos duplicados o grabar de esta manera.

new_user = old_user.dup
Foram Thakral
fuente
2

La forma fácil es:

#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     
ThienSuBS
fuente
2

Aquí hay una muestra de cómo anular el #dupmétodo ActiveRecord para personalizar la duplicación de instancias e incluir también la duplicación de relaciones:

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: este método no requiere ninguna gema externa, pero requiere una versión más reciente de ActiveRecord con el #dupmétodo implementado

Zoran Majstorovic
fuente
0

También puedes consultar la gema act_as_inheritable .

"Acts As Inheritable es una gema rubí escrita específicamente para los modelos Rails / ActiveRecord. Está destinada a ser utilizada con la Asociación autorreferencial o con un modelo que tenga un padre que comparta los atributos heredables. Esto le permitirá heredar cualquier atributo o relación del modelo padre ".

Al agregar acts_as_inheritablea sus modelos, tendrá acceso a estos métodos:

herencia_atributos

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

heredar_relaciones

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

Espero que esto le pueda ayudar.

esbanarango
fuente
0

Como podría haber más lógica, al duplicar un modelo, sugeriría crear una nueva clase, donde maneje toda la lógica necesaria. Para aliviar eso, hay una gema que puede ayudar: payaso

Según sus ejemplos de documentación, para un modelo de Usuario:

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

Creas tu clase de clonador:

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

y luego úsalo:

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

Ejemplo copiado del proyecto, pero le dará una visión clara de lo que puede lograr.

Para un registro rápido y simple, iría con:

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

Paulo Fidalgo
fuente