¿Cómo 'validar' en dañarán en los carriles


65

en la destrucción de un recurso de descanso, quiero garantizar un par de cosas antes de permitir una operación de destruir a continuar? Básicamente, quiero la capacidad de detener la operación de destrucción si noto que al hacerlo colocaría la base de datos en un estado inválido. No hay devoluciones de llamada de validación en una operación de destrucción, entonces, ¿cómo se puede "validar" si se debe aceptar una operación de destrucción?

  0

relacionadas: http://stackoverflow.com/questions/5520320/validate-before-destroy 08 nov. 112011-11-08 16:31:22

55

Puede provocar una excepción que luego se captura. Rails wraps elimina en una transacción, lo que ayuda a las cosas.

Por ejemplo:

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 

Alternativamente, puede utilizar la devolución de llamada before_destroy. Esta devolución de llamada normalmente se usa para destruir registros dependientes, pero puede lanzar una excepción o agregar un error en su lugar.

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 ahora volverá falso, y myBooking.errors se rellenarán en la declaración.

  0

Um, ¿qué pasa con sólo comprobar la asociación booking_payments que debe ser definido en ese modelo en lugar de llamar BookingPayment .count, lo que lleva a un código feo? 24 sep. 082008-09-24 00:37:25

  0

He editado mi respuesta en consecuencia. Lo siento, saqué esto de un código antiguo ... 24 sep. 082008-09-24 18:26:27

+3

Ten en cuenta que cuando ahora dice "... vale, sigue adelante y destruye", necesitas poner "super", así que el método de destrucción original se llama realmente. 19 ago. 092009-08-19 15:13:02

+23

No creo que el método de devolución de llamada funcione más. 17 jun. 102010-06-17 06:58:37

+3

errors.add_to_base está en desuso en Rails 3. En su lugar, debe hacer errors.add (: base, "message"). 06 ene. 122012-01-06 00:50:30

+8

Rails no valida antes de destruir, por lo que before_destroy debería devolver false para cancelar la destrucción. Solo agregar errores es inútil. 10 abr. 122012-04-10 17:50:11

  0

Se actualizó el código para reflejar los comentarios, pero no se inició sesión. Ahora las modificaciones se han ido y el botón editar también. No sé si se abrió paso. 13 ago. 142014-08-13 09:15:46

+15

Con Rails 5, el 'false' al final de' before_destroy' es inútil. De ahora en adelante deberías usar 'throw (: abort)' (@see: http://weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/#halting-callback-chains-by- throwing-aborthttpsgithubcomrailsrailspull17227). 06 mar. 162016-03-06 21:57:50


3

También puede utilizar la devolución de llamada before_destroy a lanzar una excepción.


5

Las asociaciones de ActiveRecord has_many y has_one permiten una opción dependiente que asegurará que las filas de la tabla relacionadas se eliminen en delete, pero esto generalmente es para mantener su base de datos limpia en lugar de evitar que no sea válida.

+1

+1 para "\ _" no se dio cuenta de que era una opción en SO 25 sep. 092009-09-25 18:47:32

+1

Otra forma de tener en cuenta los guiones bajos, si son parte de un nombre de función o similar, es envolverlos en puntos reutilizables. Eso se mostrará luego como código, 'like_so'. 01 feb. 132013-02-01 01:37:07


5

Puede envolver la acción destruir en un "if" en el controlador:

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

Dónde valid_destroy? es un método en su clase de modelo que devuelve verdadero si se cumplen las condiciones para destruir un registro.

Tener un método como este también le permitirá evitar que la pantalla de la opción de borrar para el usuario - que mejorarán la experiencia de usuario que el usuario no será capaz de realizar una operación ilegal.

+6

Bucle infinito, ¿alguien? 16 mar. 112011-03-16 20:19:02

+1

buena captura, pero suponía que este método está en el controlador, difiriendo el modelo. Si estuviera en el modelo definitivamente causaría problemas 16 mar. 112011-03-16 23:30:31

  0

jeje, perdón por que ... Veo lo que quiere decir, acabo de ver "método en su clase de modelo" y rápidamente pensé "uh oh", pero tiene razón - destruir en el controlador, eso funcionaría bien. :) 17 mar. 112011-03-17 21:42:50

  0

todo bien, de hecho es mejor ser muy claro en lugar de dificultar la vida de algunos principiantes pobres con poca claridad 17 mar. 112011-03-17 23:57:10


46

sólo una nota:

Para carriles 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 este enfoque es que la devolución de llamada before_destroy parece llamarse * después de * todos los booking_payments han sido destruidos. 18 mar. 122012-03-18 15:01:52

+4

Boleto relacionado: https://github.com/rails/rails/issues/3458 @sunkencity puede declarar before_destroy antes de la declaración de la asociación para evitarlo temporalmente. 23 abr. 132013-04-23 06:11:46


4

que terminó usando el código de aquí para crear una anulación can_destroy en 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 

Esto tiene el añadido beneficio de lo que es trivial para ocultar/mostrar un botón de borrar en la interfaz de usuario


3

tengo estas clases o modelos

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 

Ahora cuando se elimina una empresa este proceso valida si hay productos asociados con empresas Nota: Tienes que escribir esto en la parte superior de la clase con el fin de validarla primero.


13

Es lo que hice con los carriles 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

Este es un buen artículo que explica este comportamiento en Rails 5: http://blog.bigbinary.com/2016/02/13/rails-5-does-not-halt-callback-chain-when-false-is- returned.html 07 sep. 162016-09-07 11:15:31


1

Uso ActiveRecord validación de contexto en los carriles 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