438 votes

Quel est le moyen le plus simple de dupliquer un enregistrement actif ?

Je veux faire une copie d'un objet ActiveRecord, en changeant un seul champ au cours du processus (en plus de l'objet ActiveRecord). id ). Quel est le moyen le plus simple d'y parvenir ?

Je réalise que je pourrais créer un nouvel enregistrement, puis itérer sur chacun des champs en copiant les données champ par champ - mais je me suis dit qu'il devait y avoir un moyen plus simple de le faire.

Peut-être quelque chose comme ça :

 new_record = Record.copy(:id)

654voto

Michael Sepcot Points 5455

Pour obtenir une copie, utilisez le dup (ou clone pour < rails 3.1+) :

#rails >= 3.1
new_record = old_record.dup

# rails < 3.1
new_record = old_record.clone

Vous pouvez ensuite modifier les champs que vous souhaitez.

ActiveRecord remplace la fonction intégrée Object#clone. pour vous donner un nouvel enregistrement (non sauvegardé dans la BD) avec un ID non attribué.
Notez qu'il ne copie pas les associations, vous devrez donc le faire manuellement si vous en avez besoin.

Le clone de Rails 3.1 est une copie superficielle, utilisez plutôt dup...

6 votes

Cela fonctionne-t-il toujours dans Rails 3.1.0.beta ? Lorsque je fais q = p.clone et ensuite p == q j'obtiens true dos. D'autre part, si j'utilise q = p.dup j'obtiens false en les comparant.

1 votes

El Documentation de Rails 3.1 sur clone dit qu'il fonctionne toujours, mais j'utilise Rails 3.1.0.rc4 et même l'option new? ne fonctionne pas.

0 votes

Lorsque j'essaie de cloner puis d'enregistrer dans Rails 3.1 RC4, j'obtiens une Mysql2::Error: Duplicate entry erreur sur la clé primaire.

75voto

Phillip Koebbe Points 408

Selon vos besoins et votre style de programmation, vous pouvez également utiliser une combinaison de la nouvelle méthode de la classe et de la fusion. À défaut d'une meilleure simple Par exemple, supposons que vous ayez une tâche prévue pour une certaine date et que vous souhaitiez la dupliquer à une autre date. Les attributs réels de la tâche ne sont pas importants :

old\_task = Task.find(task\_id)
new\_task = Task.new(old\_task.attributes.merge({:scheduled\_on => some\_new\_date}))

créera une nouvelle tâche avec :id => nil , :scheduled_on => some_new_date et tous les autres attributs sont les mêmes que ceux de la tâche originale. En utilisant Task.new, vous devrez appeler explicitement save, donc si vous voulez qu'elle soit sauvegardée automatiquement, changez Task.new en Task.create.

La paix.

5 votes

Je ne suis pas sûr que ce soit une bonne idée, parce que vous obtenez WARNING: Can't mass-assign protected attributes: id, due_date, created_at, updated_at a retourné

0 votes

Lorsque je fais cela, je reçois une erreur d'attribut inconnu avec une colonne à cause d'une colonne qui est là en raison d'une relation has_many. Existe-t-il un moyen de contourner ce problème ?

3 votes

@RubenMartineJr. Je sais que c'est un vieux post, mais vous pouvez contourner ce problème en utilisant '.except' sur le hash des attributs : new_task = Task.new(old_task.attributes.except(:attribute_you_dont_want, :another_aydw).merge({:scheduled_on => some_new_date}))

35voto

Vaughn Draughon Points 681

Vous pouvez également aimer le Gemme amibe pour ActiveRecord 3.2.

Dans votre cas, vous voudrez probablement utiliser l'outil de gestion de l'information. nullify , regex ou prefix les options disponibles dans le DSL de configuration.

Il prend en charge la duplication récursive facile et automatique de has_one , has_many y has_and_belongs_to_many les associations, le prétraitement sur le terrain et un DSL de configuration très souple et puissant qui peut être appliqué à la fois au modèle et à la volée.

n'oubliez pas de consulter le Documentation sur les amibes mais l'utilisation est assez facile...

juste

gem install amoeba

ou ajouter

gem 'amoeba'

à votre Gemfile

puis ajoutez le bloc amibe à votre modèle et exécutez la commande dup méthode habituelle

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

Vous pouvez également contrôler les champs qui sont copiés de nombreuses façons, mais par exemple, si vous voulez empêcher les commentaires d'être dupliqués mais que vous voulez conserver les mêmes balises, vous pouvez faire quelque chose comme ceci :

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

  amoeba do
    exclude_field :comments
  end
end

Vous pouvez également prétraiter les champs afin d'indiquer leur caractère unique à l'aide de préfixes, de suffixes et de regex. En outre, de nombreuses options vous permettent d'écrire dans le style le plus lisible pour votre objectif :

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 copie récursive des associations est facile, il suffit d'activer amoeba sur les modèles enfants également.

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

Le DSL de configuration offre encore plus d'options, alors n'oubliez pas de consulter la documentation.

Profitez-en ! :)

0 votes

Excellente réponse. Merci pour les détails !

0 votes

Merci cela fonctionne ! Mais j'ai une question : comment puis-je ajouter de nouvelles entrées avec le clonage avant de sauvegarder l'objet cloné ?

1 votes

Juste une réparation ici. La méthode correcte est .amoeba_dup pas seulement .dup . J'ai essayé d'exécuter ce code, mais il ne fonctionnait pas ici.

31voto

bradgonesurfing Points 8600

Utilice ActiveRecord::Base#dup si vous ne voulez pas copier l'id

1 votes

@Thorin selon la réponse acceptée ci-dessus, il semble que la méthode correcte pour Rails < 3.1 soit la suivante .clone

25voto

En général, je me contente de copier les attributs, en modifiant ce qui doit l'être :

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

0 votes

Lorsque je fais cela, j'obtiens un unknown attribute erreur avec une colonne à cause d'une colonne qui est là à cause d'une relation has_many. Existe-t-il un moyen de contourner ce problème ?

0 votes

Avec rails4, il ne crée pas un identifiant unique pour l'enregistrement

4 votes

Pour créer un nouvel enregistrement avec Rails 4, procédez comme suit User.create(old_user.attributes.merge({ login: "newlogin", id: nil })) . Cela permettra d'enregistrer un nouvel utilisateur avec l'identifiant unique correct.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X