web-dev-qa-db-fra.com

Comment puis-je «valider» sur destroy dans rails

Lors de la destruction d'une ressource reposante, je veux garantir certaines choses avant de permettre à une opération de destruction de continuer? Fondamentalement, je veux pouvoir arrêter l'opération de destruction si je constate que cela mettrait 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?

72
Stephen Cagle

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 lever une exception ou ajouter une erreur à la place.

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 false et myBooking.errors sera renseigné au retour.

63
Airsource Ltd

juste une note:

Pour 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
47
workdreamer

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
18
Raphael Monteiro

Les associations ActiveRecord has_many et has_one permettent une option dépendante qui garantira que les lignes de table liées sont supprimées lors de la suppression, mais cela est généralement pour garder votre base de données propre plutôt que de l'empêcher d'être invalide.

6
go minimal

Vous pouvez encapsuler l'action destroy dans une instruction "if" dans le contrôleur:

def destroy # in controller context
  if (model.valid_destroy?)
    model.destroy # if in model context, use `super`
  end
end

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.

Avoir une méthode comme celle-ci vous permettra également d'empêcher l'affichage de l'option de suppression pour l'utilisateur - ce qui améliorera l'expérience utilisateur car l'utilisateur ne pourra pas effectuer une opération illégale.

5
Toby Hede

J'ai fini par utiliser le code d'ici pour créer un remplacement de 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 a l'avantage supplémentaire de rendre trivial le masquage/affichage d'un bouton de suppression sur l'interface utilisateur.

4
Hugo Forte

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 s'il existe des produits associés à des entreprises Remarque: vous devez écrire ceci en haut de la classe afin de le valider en premier.

3
Mateo Vidal

Vous pouvez également utiliser le rappel before_destroy pour déclencher une exception.

3
MattW.

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
2
swordray

J'espérais que cela serait pris en charge, j'ai donc ouvert un problème Rails pour l'ajouter:

https://github.com/Rails/rails/issues/32376

1
ragurney