Clonage facile d'objets active_record comprenant des associations et plusieurs opérations sous associations et attributs.
Voir ici.
L'objectif était de pouvoir reproduire facilement et rapidement des objets ActiveRecord y compris leurs enfants, par exemple en copiant un article de blog en conservant ses balises ou catégories associées.
Cette gemme est nommée « Amibe » parce que les amibes sont (de petites formes de vie) capables de se reproduire. Leurs enfants et petits-enfants se reproduisent également rapidement et facilement.
Une gemme d'extension ActiveRecord pour permettre la duplication des objets d'enregistrement enfant associés lors de la duplication d'un modèle d'enregistrement actif.
Compatibles Rails 5.2, 6.0, 6.1. Pour Rails 4.2 à 5.1, utilisez la version 3.x.
Prend en charge les types d'association suivants
has_many
has_one :through
has_many :through
has_and_belongs_to_many
Un simple DSL pour configurer les champs à copier. Le DSL peut être appliqué à vos modèles de rails ou utilisé à la volée.
Prend en charge les enfants STI (Single Table Inheritance) héritant des paramètres de leur amibe parent.
Plusieurs styles de configuration tels que inclusif, exclusif et aveugle (c'est-à-dire tout copier).
Prend en charge le clonage des enfants d'enregistrements Many-to-Many ou simplement le maintien des associations originales
Prend en charge l'exploration automatique, c'est-à-dire la copie récursive des enregistrements d'enfants et de petits-enfants.
Prend en charge le prétraitement des champs pour aider à indiquer l'unicité et garantir l'intégrité de vos données en fonction de vos besoins en logique métier, par exemple en préfixant « Copie de » ou un texte similaire.
Prend en charge le prétraitement des champs avec des blocs lambda personnalisés afin que vous puissiez faire pratiquement ce que vous voulez si, par exemple, vous avez besoin d'une logique personnalisée lors de la création de copies.
Amoeba peut effectuer les opérations de prétraitement suivantes sur les champs des enregistrements copiés
ensemble
ajouter un préfixe
ajouter
annuler
personnaliser
expression régulière
j'espère que c'est ce à quoi vous vous attendez :
gem installer l'amibe
ou ajoutez-le simplement à votre Gemfile :
gemme 'amibe'
Configurez vos modèles avec l'un des styles ci-dessous, puis exécutez simplement la méthode amoeba_dup
sur votre modèle où vous exécuteriez normalement la méthode dup
:
p = Post.create(:title => "Bonjour tout le monde !", :content => "Lorum ipsum dolor")p.comments.create(:content => "Je l'adore !")p.comments.create(: content => "C'est nul !")puts Comment.all.count # devrait être 2my_copy = p.amoeba_dupmy_copy.saveputs Comment.all.count # devrait être 4
Par défaut, lorsqu'elle est activée, amibe copiera automatiquement tous les enregistrements enfants associés et les associera au nouvel enregistrement parent.
Vous pouvez configurer le comportement pour inclure uniquement les champs que vous répertoriez ou pour inclure uniquement les champs que vous n'excluez pas. Des trois, le plus performant sera le style aveugle, suivi du style inclusif, et le style exclusif sera le plus lent en raison de la nécessité d'une vérification explicite supplémentaire sur chaque champ. Cette différence de performances est probablement suffisamment négligeable pour que vous puissiez choisir le style à utiliser en fonction de celui qui est le plus facile à lire et à écrire. Cependant, si votre arborescence de données est suffisamment grande et que vous avez besoin de contrôler les champs à copier, le style inclusif est probablement préférable. choix que le style exclusif.
Veuillez noter que ces exemples ne sont que des approximations de scénarios du monde réel et peuvent ne pas être particulièrement réalistes. Ils ont uniquement pour but de démontrer l'utilisation des fonctionnalités.
Il s'agit du cas d'utilisation le plus basique et permettra simplement de copier toutes les associations connues.
Si vous avez des modèles pour un blog comme celui-ci :
classe Post < ActiveRecord :: Base has_many :commentsendclass Commentaire < ActiveRecord :: Base appartient_à :postend
ajoutez simplement le bloc de configuration amibe à votre modèle et appelez la méthode activate pour activer la copie des enregistrements enfants, comme ceci :
classe Post < ActiveRecord :: Base has_many : commentaires amibe réalisable Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Les enregistrements enfants seront automatiquement copiés lorsque vous exécuterez la méthode amoeba_dup
.
Si vous souhaitez copier uniquement certaines associations mais pas d'autres, vous pouvez utiliser le style inclusif :
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags has_many :auteurs amibe doenableinclude_association :tagsinclude_association :auteurs Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Utiliser le style inclusif dans le bloc amibe implique en fait que vous souhaitez activer l'amibe, il n'est donc pas nécessaire d'exécuter la méthode d'activation, même si cela ne fera pas de mal non plus :
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags has_many :auteurs amibe doinclude_association :tagsinclude_association :auteurs Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Vous pouvez également spécifier les champs à copier en passant un tableau. Si vous appelez include_association
avec une seule valeur, elle sera ajoutée à la liste des champs déjà inclus. Si vous transmettez un tableau, votre tableau écrasera les valeurs d'origine.
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags has_many :auteurs amibe doinclude_association [:tags, :authors] Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Ces exemples copieront les balises et les auteurs de la publication, mais pas ses commentaires.
Le style inclusif, lorsqu'il est utilisé, désactivera automatiquement tout autre style précédemment sélectionné.
Si vous avez plus de champs à inclure qu'à exclure, vous souhaiterez peut-être réduire la quantité de saisie et de lecture que vous devez effectuer en utilisant le style exclusif. Tous les champs qui ne sont pas explicitement exclus seront copiés :
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags has_many :auteurs amibe doexclude_association : commentaires Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Cet exemple fait la même chose que l'exemple de style inclusif, il copiera les balises et les auteurs de la publication mais pas ses commentaires. Comme pour le style inclusif, il n'est pas nécessaire d'activer explicitement amoeba lors de la spécification des champs à exclure.
Le style exclusif, lorsqu'il est utilisé, désactivera automatiquement tout autre style précédemment sélectionné. Ainsi, si vous avez sélectionné des champs d'inclusion, puis que vous choisissez des champs d'exclusion, la méthode exclude_association
désactivera le style inclusif précédemment sélectionné et effacera tous les champs d'inclusion correspondants. .
De plus, si vous avez besoin de cheminer une condition supplémentaire pour une relation d'inclusion ou d'exclusion, vous pouvez cheminer le nom de la méthode vers l'option :if
.
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags amibe doinclude_association :comments, if: :popular? fin vraiment populaire ? j'aime > 15 fin
Après avoir appelé Post.first.amoeba_dup
si likes
sont supérieurs à 15, tous les commentaires seront également dupliqués, mais dans une autre situation, aucune relation ne sera clonée. Le même comportement sera pour exclude_association
.
Soyez conscient ! Si vous écriviez :
classe Post < ActiveRecord :: Base has_many : commentaires has_many : tags amibe doexclude_association :tagsinclude_association :comments, if: :popular? fin vraiment populaire ? j'aime > 15 fin
La stratégie d'inclusion sera-t-elle choisie quel que soit le résultat du popular?
appel de méthode (idem pour la situation inverse).
Si vous utilisez une relation plusieurs-à-plusieurs, vous pouvez demander à amibe de créer des doubles des enregistrements associés d'origine plutôt que de simplement maintenir l'association avec les enregistrements originaux. Le clonage est facile, il suffit d'indiquer à l'amibe quels champs cloner de la même manière que vous lui indiquez quels champs inclure ou exclure.
classe Post < ActiveRecord :: Base has_and_belongs_to_many : avertissements has_many : post_widgets has_many :widgets, :through => :post_widgets amibe doenableclone [:widgets, :warnings] endendclass Avertissement < ActiveRecord :: Base has_and_belongs_to_many :postsendclass PostWidget < ActiveRecord :: Base appartient_à : widget appartient_to :postendclass Widget < ActiveRecord :: Base has_many : post_widgets has_many :posts, :through => :post_widgetsend
Cet exemple dupliquera en fait les avertissements et les widgets dans la base de données. S'il y avait à l'origine 3 avertissements dans la base de données, lors de la duplication d'un message, vous vous retrouverez avec 6 avertissements dans la base de données. Cela contraste avec le comportement par défaut selon lequel votre nouvelle publication serait simplement réassociée à tous les avertissements existants et ces avertissements eux-mêmes ne seraient pas dupliqués.
Par défaut, amiba reconnaît et tente de copier tous les enfants des types d'association suivants :
en a un
a beaucoup
a et appartient à beaucoup
Vous pouvez contrôler les types d'association auxquels l'amibe s'applique en utilisant la méthode recognize
dans le bloc de configuration de l'amibe.
classe Post < ActiveRecord :: Base has_one :config has_many : commentaires has_and_belongs_to_many : tags amibe dorecognize [:has_one, :has_and_belongs_to_many] Commentaire endendclass < ActiveRecord :: Base appartient_to :postendclass Balise < ActiveRecord :: Base has_and_belongs_to_many : postsend
Cet exemple copiera les données de configuration de la publication et conservera les balises associées à la nouvelle publication, mais ne copiera pas les commentaires de la publication car l'amibe ne reconnaîtra et ne copiera que les enfants des associations has_one
et has_and_belongs_to_many
et dans cet exemple, les commentaires ne sont pas une association has_and_belongs_to_many
.
Si vous souhaitez empêcher un champ normal (non basé sur une association has_*
) de conserver sa valeur une fois copié, vous pouvez "mettre à zéro" ou "annuler" le champ, comme ceci :
Sujet de classe < ActiveRecord :: Base has_many :postsendclass Publier < ActiveRecord :: Base appartient_à : sujet has_many : commentaires amibe doenablenullify :date_publishednullify :topic_id Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Cet exemple copiera tous les commentaires d'une publication. Cela annulera également la date de publication et dissociera le message de son sujet d'origine.
Contrairement aux styles inclusifs et exclusifs, la spécification de champs nuls ne permettra pas automatiquement à amibe de copier tous les enregistrements enfants. Comme pour tout objet d'enregistrement actif, la valeur du champ par défaut sera utilisée au lieu de nil
si une valeur par défaut existe sur la migration.
Si vous souhaitez simplement définir un champ sur une valeur arbitraire sur tous les objets dupliqués, vous pouvez utiliser la directive set
. Par exemple, si vous souhaitez copier un objet auquel est associé un processus d'approbation, vous souhaiterez probablement définir à nouveau l'état du nouvel objet sur ouvert ou « en cours ».
classe Post < ActiveRecord :: Base amibe doset :state_tracker => "open_for_editing" fin
Dans cet exemple, lorsqu'une publication est dupliquée, son champ state_tracker
recevra toujours la valeur open_for_editing
pour démarrer.
Vous pouvez ajouter une chaîne au début du champ d'un objet copié pendant la phase de copie :
classe Post < ActiveRecord :: Base amibe doenableprepend :title => "Copie de " fin
Vous pouvez ajouter une chaîne à la fin du champ d'un objet copié pendant la phase de copie :
classe Post < ActiveRecord :: Base amibe doenableappend :title => "Copie de " fin
Vous pouvez exécuter une requête de recherche et de remplacement sur le champ d'un objet copié pendant la phase de copie :
classe Post < ActiveRecord :: Base amibe doenableregex :contents => {:replace => /dog/, :with => 'cat'} fin
Vous pouvez exécuter une ou plusieurs méthodes personnalisées pour faire pratiquement tout ce que vous voulez, passez simplement un bloc lambda ou un tableau de blocs lambda à la directive customize
. Chaque bloc doit avoir la même forme, ce qui signifie que chaque bloc doit accepter deux paramètres, l'objet d'origine et l'objet nouvellement copié. Vous pouvez alors faire ce que vous voulez, comme ceci :
classe Post < ActiveRecord :: Base amibe doprepend :title => "Bonjour tout le monde ! "customize(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "... sais quoi Je dis?" fin
ou ceci, en utilisant un tableau :
classe Post < ActiveRecord :: Base has_and_belongs_to_many : tags amibe doinclude_association :tagscustomize([ lambda do |orig_obj,copy_of_obj|# de bonnes choses vont ici à la fin, lambda do |orig_obj,copy_of_obj|# d'autres bonnes choses vont ici à la fin]) fin
Les blocs Lambda transmis à la personnalisation s'exécutent, par défaut, après toute copie et prétraitement des champs. Si vous souhaitez exécuter une méthode avant toute personnalisation ou prétraitement de champ, vous pouvez utiliser override
le cousin de customize
. L'utilisation est la même que ci-dessus.
classe Post < ActiveRecord :: Base amibe doprepend :title => "Bonjour tout le monde ! "override(lambda { |original_post,new_post| if original_post.foo == "bar"new_post.baz = "qux" end})append :comments => "... je sais quoi Je dis?" fin
Vous pouvez appliquer un seul préprocesseur à plusieurs champs à la fois.
classe Post < ActiveRecord :: Base amibe doenableprepend :title => "Copie de ", :contents => "Contenu copié : " fin
Vous pouvez appliquer plusieurs directives de prétraitement à un seul modèle à la fois.
classe Post < ActiveRecord :: Base amibe doprepend :title => "Copie de ", :contents => "Contenu original : "append :contents => " (version copiée)"regex :contents => {:replace => /dog/, :with => ' chat'} fin
Cet exemple devrait donner quelque chose comme ceci :
post = Post.create( :title => "Bonjour tout le monde", :contents => "J'aime les chiens, les chiens sont géniaux.")new_post = post.amoeba_dupnew_post.title # "Copie de Hello world"new_post.contents # "Contenu original : J'aime les chats, les chats sont géniaux. (version copiée)"
Comme nullify
, les directives de prétraitement n'activent pas automatiquement la copie des enregistrements enfants associés. Si seules les directives de prétraitement sont utilisées et que vous souhaitez copier les enregistrements enfants et qu'aucune liste include_association
ou exclude_association
n'est fournie, vous devez toujours activer explicitement la copie des enregistrements enfants en appelant la méthode activate à partir du bloc amibe sur votre modèle.
Vous pouvez utiliser une combinaison de méthodes de configuration dans le bloc amibe de chaque modèle. Les types d'associations reconnus ont priorité sur les listes d'inclusion ou d'exclusion. Le style inclusif prime sur le style exclusif, et ces deux styles explicites priment sur le style aveugle. En d'autres termes, si vous listez les champs à copier, amibe copiera uniquement les champs que vous listez, ou copiera uniquement les champs que vous n'excluez pas, selon le cas. De plus, si un type de champ n'est pas reconnu, il ne sera pas copié, qu'il apparaisse ou non dans une liste d'inclusion. Si vous souhaitez qu'amibe copie automatiquement tous les enregistrements de vos enfants, ne répertoriez aucun champ en utilisant include_association
exclude_association
.
L'exemple de syntaxe suivant est parfaitement valide et entraînera l'utilisation d'un style inclusif. L'ordre dans lequel vous appelez les méthodes de configuration dans le bloc amibe n'a pas d'importance :
Sujet de classe < ActiveRecord :: Base has_many :postsendclass Publier < ActiveRecord :: Base appartient_à : sujet has_many : commentaires has_many : tags has_many :auteurs amibe doexclude_association :authorsinclude_association :tagsnullify :date_publishedprepend :title => "Copie de "append :contents => " (version copiée)"regex :contents => {:replace => /dog/, :with => 'cat'}include_association :authorsenablenullify :topic_id Commentaire endendclass < ActiveRecord :: Base appartient_à :postend
Cet exemple copiera toutes les balises et tous les auteurs d'une publication, mais pas ses commentaires. Cela annulera également la date de publication et dissociera le message de son sujet d'origine. Il prétraitera également les champs de la publication comme dans l'exemple de prétraitement précédent.
Notez qu'en raison de la priorité, le style inclusif est utilisé et la liste des champs d'exclusion n'est jamais consultée. De plus, la méthode enable
est redondante car l'amibe est automatiquement activée lors de l'utilisation include_association
.
Les directives de prétraitement sont exécutées après la copie des enregistrements enfants et sont exécutées dans cet ordre.
Champs nuls
Ajoute
Ajoute
Rechercher et remplacer
Les directives de prétraitement n’affectent pas les listes d’inclusion et d’exclusion.
Vous pouvez faire en sorte que l'amibe continue à copier le long de la chaîne aussi loin que vous le souhaitez, ajoutez simplement des blocs d'amibe à chaque modèle dont vous souhaitez copier ses enfants. Amoeba réapparaîtra automatiquement dans tous les petits-enfants activés et les copiera également.
classe Post < ActiveRecord :: Base has_many : commentaires amibe réalisable Commentaire endendclass < ActiveRecord :: Base appartient_à :post has_many : notes amibe réalisable note de fin de classe < ActiveRecord :: Base appartient_à :commentend
Dans cet exemple, lorsqu'une publication est copiée, amoeba copiera tous les commentaires d'une publication ainsi que les notes de chaque commentaire.
Utiliser l'association has_one :through
est simple, assurez-vous simplement d'activer l'amibe sur chaque modèle avec une association has_one
et l'amibe explorera automatiquement et récursivement, comme ceci :
classe Fournisseur < ActiveRecord :: Base has_one :compte has_one :historique, :through => :compte amibe réalisable Compte endendclass < ActiveRecord :: Base appartient_à :fournisseur has_one : historique amibe réalisable Historique de la classe de fin < ActiveRecord :: Base appartient_à :compte
La copie de has_many :through
associations fonctionne automatiquement. Ils effectuent la copie de la même manière que l'association has_and_belongs_to_many
, ce qui signifie que les enregistrements enfants réels ne sont pas copiés, mais que les associations sont simplement conservées. Vous pouvez ajouter des préprocesseurs de terrain au modèle intermédiaire si vous le souhaitez, mais ce n'est pas strictement nécessaire :
Assemblage de classe < ActiveRecord :: Base has_many : manifestes has_many :parts, :through => :manifestes amibe réalisable manifeste de classe endend < ActiveRecord :: Base appartient_to :assembly appartient_à :partie amibe doprepend :notes => "Copie de " partie endendclass < ActiveRecord :: Base has_many : manifestes has_many :assemblies, :through => :manifestes amibe réalisable fin
Vous pouvez contrôler la façon dont amibe copie votre objet, à la volée, en passant un bloc de configuration à la méthode amibe du modèle. La méthode de configuration est statique mais la configuration est appliquée instance par instance.
classe Post < ActiveRecord :: Base has_many : commentaires amibe doenableprepend :title => "Copie de " Commentaire endendclass < ActiveRecord :: Base appartient_to : postendclass PostsController < ActionController def duplicate_a_postold_post = Post.create( :title => "Bonjour tout le monde", :contents => "Lorum ipsum")old_post.class.amoeba do prepend :contents => "Voici une copie : "endnew_post = old_post.amoeba_dupnew_post.title # devrait être "Copie de Hello world" new_post.contents # devrait être "Voici une copie : Lorum ipsum"new_post.save fin
Si vous utilisez l'héritage de table unique fourni par ActiveRecord, vous pouvez amener l'amibe à traiter automatiquement les classes enfants de la même manière que leurs parents. Tout ce que vous avez à faire est d'appeler la méthode propagate
dans le bloc amibe de la classe parent et toutes les classes enfants doivent copier de la même manière.
create_table :products, :force => true do |t| t.string :type # ceci est la colonne STI # ceux-ci appartiennent à tous les produits t.string :titre t.décimal : prix # ce sont uniquement pour les chemises t.decimal : sleeve_length t.decimal : collar_size # ce sont uniquement pour les ordinateurs t.entier : ram_size t.integer :hard_drive_sizeendclass Produit < ActiveRecord::Base has_many : images has_and_belongs_to_many : catégories amibe doenablepropagate Chemise endendclass < Ordinateur Productendclass < Productendclass ProductsController def some_methodmy_shirt = Shirt.find(1)my_shirt.amoeba_dupmy_shirt.save# cette chemise devrait maintenant :# - avoir sa propre copie de toutes les images parent# - être dans les mêmes catégories que le parent fin
Cet exemple doit dupliquer toutes les images et sections associées à cette chemise, qui est un enfant de Product.
Par défaut, la propagation utilise la parentalité soumise, ce qui signifie que les paramètres de configuration du parent seront appliqués, mais tous les paramètres enfants, s'ils sont présents, s'ajouteront ou écraseront les paramètres du parent en fonction de la manière dont vous appelez les méthodes DSL.
Vous pouvez modifier ce comportement, appelé "style parental", pour donner la préférence aux paramètres parents ou ignorer tous les paramètres enfants.
Le style parental :relaxed
préférera les paramètres parentaux.
classe Produit < ActiveRecord :: Base has_many : images has_and_belongs_to_many : sections amibe doexclude_association :imagespropagate :relaxed Chemise endendclass < Produit include_association :images include_association : sections prepend :title => "Copie de "fin
Dans cet exemple, les paramètres include_association
en conflit sur l'enfant seront ignorés et le paramètre exclude_association
parent sera utilisé,