Domanda

Alla distruzione di una risorsa riposante, voglio garantire alcune cose prima di consentire che l'operazione di distruzione continui? Fondamentalmente, voglio la possibilità di interrompere l'operazione di distruzione se noto che farlo porterebbe il database in uno stato non valido? Non ci sono callback di convalida su un'operazione di distruzione, quindi come si fa a "validare" se un'operazione di distruzione deve essere accettata?

È stato utile?

Soluzione

Puoi sollevare un'eccezione che poi catturi. Rails esegue il wrapping delle eliminazioni in una transazione, il che aiuta le cose.

Ad esempio:

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

In alternativa puoi usare il callback before_destroy. Questo callback viene normalmente utilizzato per distruggere i record dipendenti, ma è possibile generare un'eccezione o aggiungere un errore.

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 ora restituirà false e myBooking.errors verrà popolato al ritorno.

Altri suggerimenti

solo una nota:

Per rotaie 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

È quello che ho fatto con 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

Le associazioni ActiveRecord has_many e has_one consentono un'opzione dipendente che garantirà l'eliminazione delle righe della tabella correlate durante l'eliminazione, ma di solito serve a mantenere pulito il database anziché impedirne l'invalidità.

Puoi concludere l'azione di distruzione in un " if " dichiarazione nel controller:

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

Dove valid_destroy? è un metodo sulla tua classe di modello che restituisce vero se sono soddisfatte le condizioni per distruggere un record.

Avere un metodo come questo ti permetterà anche di impedire la visualizzazione dell'opzione di eliminazione per l'utente, il che migliorerà l'esperienza dell'utente in quanto l'utente non sarà in grado di eseguire un'operazione illegale.

Ho finito per usare il codice da qui per creare una sostituzione can_destroy su 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

Questo ha l'ulteriore vantaggio di rendere banale nascondere / mostrare un pulsante di eliminazione sull'interfaccia utente

Puoi anche usare il callback before_destroy per sollevare un'eccezione.

Ho queste classi o modelli

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

Ora quando elimini un'impresa questo processo convalida se ci sono prodotti associati alle imprese Nota: devi scriverlo nella parte superiore della classe per convalidarlo prima.

Utilizza la convalida del contesto ActiveRecord in 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

Speravo che questo fosse supportato, quindi ho aperto un problema di rotaie per ottenerlo aggiunto:

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top