Come "convalidare" su destroy in rail


65

In caso di distruzione di una risorsa restful, voglio garantire alcune cose prima di consentire l'esecuzione di un'operazione di distruzione? Fondamentalmente, voglio la possibilità di fermare l'operazione di distruzione se osservo che fare ciò metterebbe il database in uno stato non valido? Non ci sono callback di convalida su un'operazione di distruzione, quindi come si "convalida" se deve essere accettata un'operazione di distruzione?

  0
55

È possibile generare un'eccezione che viene rilevata. Rails avvolge le eliminazioni in una transazione, il che aiuta a fare la differenza.

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 è possibile utilizzare 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 sarà ora return false, e myBooking.errors sarà popolata al ritorno.

  0

Uhm, cosa c'è di sbagliato con solo controllando l'associazione booking_payments che dovrebbe essere definito in quel modello invece di chiamare BookingPayment . Conto, portando a brutto codice? 24 set. 082008-09-24 00:37:25

  0

Ho modificato la mia risposta di conseguenza. Scusa, l'ho estratto da un vecchio codice ... 24 set. 082008-09-24 18:26:27

+3

Nota che dove ora dice "... ok, vai avanti e distruggi", devi mettere "super", così viene effettivamente chiamato il metodo di distruzione originale. 19 ago. 092009-08-19 15:13:02

+23

Non penso che il metodo di callback funzioni più. 17 giu. 102010-06-17 06:58:37

+3

errors.add_to_base è deprecato in Rails 3. Invece dovresti fare errors.add (: base, "message"). 06 gen. 122012-01-06 00:50:30

+8

Rails non convalida prima di distruggere, quindi before_destroy dovrebbe restituire false per annullare la distruzione. L'aggiunta di errori è inutile. 10 apr. 122012-04-10 17:50:11

  0

È stato aggiornato il codice per riflettere i commenti, ma non è stato effettuato il login. Ora le modifiche sono sparite e anche il pulsante di modifica. Non so se è passato attraverso. 13 ago. 142014-08-13 09:15:46

+15

Con Rails 5, il 'false' alla fine di' before_destroy' è inutile. D'ora in poi dovresti usare 'throw (: abort)' (@see: http://weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/#halting-callback-chains-by- lancio-aborthttpsgithubcomrailsrailspull17227). 06 mar. 162016-03-06 21:57:50


3

È inoltre possibile utilizzare il callback before_destroy per generare un'eccezione.


5

Le associazioni ActiveRecord has_many e has_one consentono un'opzione dipendente che assicurerà che le righe della tabella correlata vengano eliminate per l'eliminazione, ma in genere per mantenere pulito il database anziché impedirne l'annullamento.

+1

+1 per "\ _" non si rendeva conto che era un'opzione su SO 25 set. 092009-09-25 18:47:32

+1

Un altro modo per occuparsi dei caratteri di sottolineatura, se fanno parte di un nome di funzione o simile, è di avvolgerli in apici inversi. Questo verrà visualizzato come codice, 'like_so'. 01 feb. 132013-02-01 01:37:07


5

Puoi avvolgere l'azione distruggere in un "if" 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 classe del modello che restituisce true se vengono soddisfatte le condizioni per la distruzione di un record.

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

+6

Ciclo infinito, chiunque? 16 mar. 112011-03-16 20:19:02

+1

buona presa, ma stavo assumendo che questo metodo è nel controller, rimandando al modello. Se fosse nel modello sarebbe sicuramente causa problemi 16 mar. 112011-03-16 23:30:31

  0

hehe, scusa per il fatto che ... Capisco cosa intendi, ho appena visto "metodo sulla tua classe modello" e ho pensato velocemente "uh oh", ma hai ragione - distruggi sul controller, funzionerebbe bene. :) 17 mar. 112011-03-17 21:42:50

  0

tutto buono, anzi meglio essere molto chiaro piuttosto che rendere difficile la vita di un povero principiante con scarsa chiarezza 17 mar. 112011-03-17 23:57:10


46

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

Un problema con questo approccio è che il callback before_destroy sembra essere chiamato * dopo * tutti i pagamenti della prenotazione sono stati distrutti. 18 mar. 122012-03-18 15:01:52

+4

Biglietto correlato: https://github.com/rails/rails/issues/3458 @sunkencity è possibile dichiarare before_destroy prima della dichiarazione di associazione per evitare temporaneamente questo. 23 apr. 132013-04-23 06:11:46


4

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 il aggiunto vantaggio di rendere banale nascondere/mostrare un pulsante di eliminazione sull'interfaccia utente


3

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 si elimina un impresa di questo processo convalida se ci sono prodotti associati con le imprese Nota: Devi scrivere questo nel primo della classe, al fine di convalidarlo primo.


13

E '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 
+2

Questo è un bell'articolo che spiega questo comportamento in Rails 5: http://blog.bigbinary.com/2016/02/13/rails-5-does-not-halt-callback-chain-when-false-is- returned.html 07 set. 162016-09-07 11:15:31


1

Usa ActiveRecord convalida contesto 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