Puis-je configurer la suppression en cascade dans Rails?

88

Je sais que c'est probablement quelque part sur Internet, mais je ne trouve pas la réponse ici sur Stackoverflow, alors j'ai pensé que je pourrais augmenter un peu la base de connaissances ici.

Je suis un débutant dans Ruby and Rails, mais mon entreprise s'y investit beaucoup, j'essaie donc de la connaître un peu plus en détail.

Il a été difficile pour moi de changer mon état d'esprit en concevant une application à partir du «modèle» plutôt que de la base de données, donc j'essaie de comprendre comment ferait tout le travail de conception que j'ai fait classiquement dans la base de données dans le Modèle de rails à la place.

La tâche la plus récente que je me suis donc donnée est de savoir comment configurer un modèle de base de données Rails pour effectuer des suppressions en cascade? Y at-il un moyen facile de le faire? Ou devrais-je entrer dans MySql et configurer cela?

matt_dev
la source

Réponses:

103

vous pouvez également définir l'option: dépendante sur: delete_all. : delete_all émettra une seule instruction SQL pour supprimer tous les enregistrements enfants. à cause de cela, l'utilisation de: delete_all peut vous donner de meilleures performances.

has_many :memberships, dependent: :delete_all
Mike Breen
la source
8
Votre explication est déroutante. Une seule instruction SQL sera utilisée, mais la méthode destroy ne sera pas appelée pour chaque ligne enfant. Vous devez utiliser destroy_all pour cela.
John Topley
@John - espérons que les modifications dissiperont la confusion. Merci d'avoir fait remarquer cela.
Mike Breen
26
Assurez-vous de bien comprendre la différence entre utiliser :delete_allet :destroypour cela. Les deux entraîneront la suppression des appartenances enfants (1 niveau pour delete [citation nécessaire] et npour destroy (si leurs enfants ont des destructions dépendantes)) à être supprimées de la base de données, mais :destroyinstancieront chaque objet enfant et exécuteront d'abord les rappels, alors :delete_allque Instruction SQL DELETE dans la base de données. :destroyest plus lent à cause de cela, mais cela vous permet d'avoir des rappels lorsqu'un enregistrement est détruit. Contournement des rails à une extrémité et instanciation potentielle n ^ x de l'autre
jstim
2
Je suggère également de configurer des clés étrangères de base de données. De cette façon, les enregistrements sont supprimés en une seule opération. Voir la réponse ci-dessous que j'ai publiée.
Hendrik
66

Ouais tu peux, si tu utilises une relation comme has_many tu fais juste ça

has_many :memberships, dependent: :destroy
Danmayer
la source
Dan, donc je suppose que ma prochaine question est si j'exécute une commande db migrate est-ce que cela l'installera dans la base de données? Ou la cascade est-elle entièrement gérée par des rails?
matt_dev
Oui, il est géré par des rails. (Assurez-vous que vous devez vraiment toujours supprimer toutes les lignes associées, cependant.)
Stein G. Strindhaug
@Matt - la ligne has_many doit être dans votre classe de modèle, la migration ne l'ajoutera pas pour vous.
Gareth
Je préfère cette solution car elle fonctionne également si le modèle dépendant a une autre relation
has_many
25

Contrairement à la réponse fournie, je suggère fortement de le faire également au niveau de la base de données. Si vous avez différents processus ou un environnement multithread, il se peut que les enregistrements ne soient pas correctement supprimés. De plus, la clé étrangère de la base de données accélère considérablement la suppression de beaucoup de données.

Comme dans la réponse suggérée, procédez comme suit:

has_many :memberships, dependent: :delete_all

Cependant, assurez-vous également de configurer un foreign_keydans une migration. De cette façon, la base de données se charge de supprimer automatiquement les enregistrements pour vous.

Pour annuler les valeurs lorsqu'une appartenance est supprimée, en supposant que vous disposez d'un modèle utilisateur:

add_foreign_key :users, :memberships, on_delete: :nullify

Vous pouvez également supprimer tous les modèles chaque fois qu'un abonnement est supprimé

add_foreign_key :users, :memberships, on_delete: :cascade
Hendrik
la source
Puis-je utiliser à la fois "has_many: memberships, depend:: delete_all" et "add_foreign_key: users,: memberships, on_delete:: cascade"? Cela fonctionnera-t-il bien?
Rubycon
2
Vous n'aurez même pas besoin de configurer le delete_alldans le modèle. La clé étrangère se chargera de tout supprimer correctement pour vous au niveau de la base de données.
Hendrik
3
Je suis curieux de savoir ce qui se passe lorsque vous faites les deux. Il semble que cela ne devrait pas avoir d'effet négatif, mais est-ce que quelqu'un a eu une mauvaise expérience avec cette pratique de faire à la fois AR et DB?
James Klein
1
Le niveau de la base de données est ce que je recherchais. Cela devrait être la réponse acceptée à mon avis. Les autres semblent ne fonctionner que si mes requêtes s'en tiennent aux opérations ActiveRecord standard.
Brett Beatty
10

Gardez simplement à l'esprit que delete_all n'exécutera aucun rappel (comme before_destroy et after_destroy) sur les enregistrements enfants.

Jarin Udom
la source
6

Il semble que ce plugin puisse vous donner ce que vous recherchez si vous voulez que les suppressions en cascade reflètent la structure réelle de la base de données:

http://www.redhillonrails.org/foreign_key_migrations.html

Le format pour l'utiliser dans une migration serait quelque chose comme ceci:

create_table :orders do |t|
  t.column :customer_id, :integer, :on_delete => :set_null, :on_update => :cascade
  ...
end
Sean McMains
la source
5
Ce lien est mort mais c'est une alternative plus récente: github.com/matthuhiggins/foreigner
gdelfino