Qual é a maneira mais fácil de duplicar um registro de registro de atividade?

412

Desejo fazer uma cópia de um registro de registro ativo, alterando um único campo no processo (além do ID ). Qual é a maneira mais simples de conseguir isso?

Sei que poderia criar um novo registro e iterar sobre cada um dos campos que copiam os dados campo por campo - mas achei que deveria haver uma maneira mais fácil de fazer isso ...

tal como:

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

Respostas:

622

Para obter uma cópia, use o método clone (ou dup for rails 3.1+):

# rails < 3.1
new_record = old_record.clone

#rails >= 3.1
new_record = old_record.dup

Em seguida, você pode alterar os campos que desejar.

O ActiveRecord substitui o clone interno do Object # para fornecer um novo registro (não salvo no banco de dados) por um ID não atribuído.
Observe que ele não copia associações, portanto, você precisará fazer isso manualmente, se precisar.

O clone do Rails 3.1 é uma cópia superficial, use o dup ...

Michael Sepcot
fonte
6
Isso ainda funciona no Rails 3.1.0.beta? Quando eu faço q = p.clone, e depois p == q, eu recebo truede volta. Por outro lado, se eu usar q = p.dup, falsevolto ao compará-las.
Autumnsault 8/02
1
Os documentos do Rails 3.1 no clone dizem que ainda funciona, mas estou usando o Rails 3.1.0.rc4 e até o new?método não está funcionando.
Turadg
12
Parece que esta funcionalidade foi substituído por dup: gist.github.com/994614
skattyadz
74
Definitivamente NÃO use clone. Como outros pôsteres mencionaram, o método clone agora delega para usar o Kernel # clone, que copiará o ID. Use ActiveRecord :: Base # dup a partir de agora
bradgonesurfing
5
Eu tenho que dizer, isso foi uma dor real. Uma simples mudança como essa na funcionalidade pretendida pode prejudicar alguns recursos importantes se você não tiver uma boa cobertura de especificações.
Matt Smith
74

Dependendo das suas necessidades e estilo de programação, você também pode usar uma combinação do novo método da classe e mesclagem. Por falta de um exemplo simples e melhor , suponha que você tenha uma tarefa agendada para uma determinada data e deseje duplicá-la para outra data. Os atributos reais da tarefa não são importantes, portanto:

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

irá criar uma nova tarefa com :id => nil, :scheduled_on => some_new_datee todos os outros atributos o mesmo que a tarefa original. Usando Task.new, você terá que chamar explicitamente salvar, portanto, se desejar salvá-lo automaticamente, altere Task.new para Task.create.

Paz.

Phillip Koebbe
fonte
5
Não tem a certeza como bom de idéia este é b / c você se WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_atvoltou
bcackerman
Quando faço isso, recebo um erro de atributo desconhecido em uma coluna devido a uma coluna que existe devido a um relacionamento has_many. Existe alguma maneira de contornar isso?
Ruben Martinez Jr.
2
@RubenMartineJr. Eu sei que esta é uma postagem antiga, mas sim, você pode contornar isso usando '.except' nos atributos hash: new_task = Task.new (old_task.attributes.except (: attribute_you_dont_want,: another_aydw) .merge ({: schedule_on => some_new_date}))
Ninigi
@PhillipKoebbe obrigado - mas e se eu quiser que o ID não seja nulo? Eu quero que os trilhos atribuam automaticamente um novo ID ao criar a duplicata - isso é possível?
precisa saber é o seguinte
1
old_task.attribtes também atribui o campo ID, infelizmente. Não está funcionando para mim
BKSpurgeon
32

Você também pode gostar da gema Ameba do ActiveRecord 3.2.

No seu caso, você provavelmente vai querer fazer uso dos nullify, regexou prefixopções disponíveis no DSL configuração.

Ele suporta a duplicação fácil e automático recursiva has_one, has_manye has_and_belongs_to_manyassociações, pré-processamento de campo e um DSL configuração altamente flexível e poderosa que pode ser aplicado tanto para o modelo e em tempo real.

verifique a documentação da ameba, mas o uso é bastante fácil ...

somente

gem install amoeba

ou adicione

gem 'amoeba'

para o seu Gemfile

adicione o bloco de ameba ao seu modelo e execute o dupmétodo como de costume

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

Você também pode controlar quais campos são copiados de várias maneiras, mas, por exemplo, se você deseja impedir que os comentários sejam duplicados, mas deseja manter as mesmas tags, pode fazer algo assim:

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

  amoeba do
    exclude_field :comments
  end
end

Você também pode pré-processar os campos para ajudar a indicar exclusividade com prefixos e sufixos, além de expressões regulares. Além disso, também existem inúmeras opções para que você possa escrever no estilo mais legível para o seu 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

A cópia recursiva de associações é fácil, basta ativar a ameba em modelos infantis também

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

A configuração DSL tem ainda mais opções, portanto, verifique a documentação.

Aproveitar! :)

Vaughn Draughon
fonte
Ótima resposta. Obrigado pelo detalhe!
Derek Prior
Graças funciona !! Mas tenho uma pergunta: como adiciono novas entradas à clonagem antes de salvar o objeto clonado?
Mohd Anas
1
Apenas uma correção aqui. O método correto é .amoeba_dup, não apenas .dup. Eu estava tentando executar esse código, mas não estava funcionando aqui.
Victor Victor
31

Use ActiveRecord :: Base # dup se você não quiser copiar o ID

bradgonesurfing
fonte
1
@Thorin de acordo com a resposta aceita acima, parece que o método correto para Rails <3.1 é.clone
Dan Weaver
24

Normalmente, apenas copio os atributos, alterando o que for necessário:

new_user = User.new(old_user.attributes.merge(:login => "newlogin"))
François Beausoleil
fonte
Quando faço isso, recebo um unknown attributeerro com uma coluna devido a uma coluna que existe devido a um relacionamento has_many. Existe alguma maneira de contornar isso?
Ruben Martinez Jr.
com rails4, ele não cria uma identificação única para o registro
Ben
4
Para criar um novo registro com o Rails 4, faça User.create(old_user.attributes.merge({ login: "newlogin", id: nil })). Isso salvará um novo usuário com o ID exclusivo correto.
RajeshM
O Rails possui Hash # exceto e Hash # fatia , potencialmente tornando o método sugerido mais poderoso e menos propenso a erros. Não há necessidade de adicionar bibliotecas adicionais, fáceis de estender.
kucaahbe
10

Se você precisar de uma cópia profunda de associações, recomendo a gema deep_cloneable .

raidfive
fonte
Eu também. Eu tentei esta jóia e funcionou pela primeira vez, muito fácil de usar.
Rob
4

No Rails 5, você pode simplesmente criar objetos duplicados ou gravar assim.

new_user = old_user.dup
Foram Thakral
fonte
2

A maneira mais fácil é:

#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

Ou

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

Aqui está uma amostra do #dupmétodo ActiveRecord de substituição para personalizar a duplicação de instância e incluir também a duplicação de relação:

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 não requer nenhuma jóia externa, mas requer uma versão mais recente do ActiveRecord com o #dupmétodo implementado

Zoran Majstorovic
fonte
0

Você também pode verificar o actions_as_inheritable gema .

"Atua como herdável é um Ruby Gem escrito especificamente para os modelos Rails / ActiveRecord. Ele deve ser usado com a Self-Referential Association ou com um modelo que tenha um pai que compartilhe os atributos herdáveis. Isso permitirá que você herde qualquer atributo ou relação do modelo pai ".

Adicionando acts_as_inheritable aos seus modelos, você terá acesso a estes métodos:

herdit_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

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

Espero que isso possa ajudá-lo.

esbanarango
fonte
0

Como poderia haver mais lógica, ao duplicar um modelo, sugiro criar uma nova classe, na qual você lida com toda a lógica necessária. Para facilitar isso, há uma jóia que pode ajudar: palhaço

Conforme seus exemplos de documentação, para um modelo de usuário:

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

Você cria sua classe cloner:

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 depois use:

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

Exemplo copiado do projeto, mas fornecerá uma visão clara do que você pode alcançar.

Para um registro rápido e simples, eu usaria:

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

Paulo Fidalgo
fonte