Comment puis-je "valider" sur détruire dans les rails
-
02-07-2019 - |
Question
Lors de la destruction d’une ressource reposante, je veux garantir certaines choses avant de permettre à une opération de destruction de se poursuivre? En gros, je veux pouvoir arrêter l’opération de destruction si je remarque que cela placerait la base de données dans un état non valide. Il n'y a pas de rappel de validation sur une opération de destruction, alors comment "valider" si une opération de destruction doit être acceptée?
La solution
Vous pouvez déclencher une exception que vous attrapez ensuite. Rails Wraps supprime dans une transaction, ce qui aide les choses.
Par exemple:
class Booking < ActiveRecord::Base
has_many :booking_payments
....
def destroy
raise "Cannot delete booking with payments" unless booking_payments.count == 0
# ... ok, go ahead and destroy
super
end
end
Vous pouvez également utiliser le rappel before_destroy. Ce rappel est normalement utilisé pour détruire les enregistrements dépendants, mais vous pouvez générer une exception ou ajouter une erreur.
def before_destroy
return true if booking_payments.count == 0
errors.add :base, "Cannot delete booking with payments"
# or errors.add_to_base in Rails 2
false
# Rails 5
throw(:abort)
end
myBooking.destroy
renverra désormais la valeur false et myBooking.errors
sera renseigné au retour.
Autres conseils
juste une note:
Pour les rails 3
class Booking < ActiveRecord::Base
before_destroy :booking_with_payments?
private
def booking_with_payments?
errors.add(:base, "Cannot delete booking with payments") unless booking_payments.count == 0
errors.blank? #return false, to not destroy the element, otherwise, it will delete.
end
C’est ce que j’ai fait avec Rails 5:
before_destroy do
cannot_delete_with_qrcodes
throw(:abort) if errors.present?
end
def cannot_delete_with_qrcodes
errors.add(:base, 'Cannot delete shop with qrcodes') if qrcodes.any?
end
Les associations ActiveRecord has_many et has_one autorisent une option dépendante qui garantit que les lignes de la table associées sont supprimées lors de la suppression, mais cela permet généralement de garder votre base de données propre au lieu de l'empêcher d'être invalide.
Vous pouvez envelopper l'action de destruction dans un " si " déclaration dans le contrôleur:
def destroy # in controller context
if (model.valid_destroy?)
model.destroy # if in model context, use `super`
end
end
Où valid_destroy? est une méthode de votre classe de modèle qui renvoie true si les conditions de destruction d'un enregistrement sont remplies.
Une telle méthode vous permettra également d’empêcher l’affichage de l’option de suppression à l’utilisateur - ce qui améliorera l’expérience de l’utilisateur car il ne pourra pas effectuer d’opération illégale.
J'ai fini par utiliser le code d'ici pour créer un remplacement can_destroy sur activerecord: https://gist.github.com/andhapp/1761098
class ActiveRecord::Base
def can_destroy?
self.class.reflect_on_all_associations.all? do |assoc|
assoc.options[:dependent] != :restrict || (assoc.macro == :has_one && self.send(assoc.name).nil?) || (assoc.macro == :has_many && self.send(assoc.name).empty?)
end
end
end
Cela présente l’avantage supplémentaire de rendre facile de masquer / afficher un bouton de suppression sur l’interface utilisateur
.Vous pouvez également utiliser le rappel before_destroy pour déclencher une exception.
J'ai ces classes ou modèles
class Enterprise < AR::Base
has_many :products
before_destroy :enterprise_with_products?
private
def empresas_with_portafolios?
self.portafolios.empty?
end
end
class Product < AR::Base
belongs_to :enterprises
end
Désormais, lorsque vous supprimez une entreprise, ce processus valide si des produits sont associés à des entreprises. Remarque: vous devez d'abord écrire ceci en haut de la classe pour pouvoir le valider.
Utilisez la validation de contexte ActiveRecord dans Rails 5.
class ApplicationRecord < ActiveRecord::Base
before_destroy do
throw :abort if invalid?(:destroy)
end
end
class Ticket < ApplicationRecord
validate :validate_expires_on, on: :destroy
def validate_expires_on
errors.add :expires_on if expires_on > Time.now
end
end
J'espérais que cela serait supporté alors j'ai ouvert un problème avec rails pour le faire ajouter: