Tras la destrucción de un recurso en reposo, quiero garantizar algunas cosas antes de permitir que continúe una operación de destrucción. Básicamente, quiero tener la capacidad de detener la operación de destrucción si noto que hacerlo colocaría la base de datos en un estado no válido. No hay devoluciones de llamada de validación en una operación de destrucción, entonces, ¿cómo se "valida" si se debe aceptar una operación de destrucción?
ruby-on-rails
ruby
callback
Stephen Cagle
fuente
fuente
Respuestas:
Puede generar una excepción que luego captura. Rails envuelve 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 usar la devolución de llamada before_destroy. Esta devolución de llamada se usa normalmente 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 devolverá falso ymyBooking.errors
se rellenará al volver.fuente
false
al finalbefore_destroy
es inútil. De ahora en adelante debería usarthrow(:abort)
(@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ).has_many :booking_payments, dependent: :restrict_with_error
solo una nota:
Para raíles 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
fuente
has_many :booking_payments, dependent: :restrict_with_error
Es lo que hice 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
fuente
has_many :qrcodes, dependent: :restrict_with_error
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 al eliminarlas, pero esto generalmente es para mantener limpia la base de datos en lugar de evitar que no sea válida.
fuente
like_so
.dependent
opciones que no permiten la eliminación de una entidad si crea registros huérfanos (esto es más relevante para la pregunta). Por ejemplodependent: :restrict_with_error
Puede envolver la acción de destrucción en una declaración "si" 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 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 se muestre la opción de eliminar al usuario, lo que mejorará la experiencia del usuario ya que el usuario no podrá realizar una operación ilegal.
fuente
Terminé usando el código de aquí para crear una anulación de 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 beneficio adicional de hacer que sea trivial ocultar / mostrar un botón de eliminación en la interfaz de usuario
fuente
Situación a partir de Rails 6:
Esto funciona:
before_destroy :ensure_something, prepend: true do throw(:abort) if errors.present? end private def ensure_something errors.add(:field, "This isn't a good idea..") if something_bad end
validate :validate_test, on: :destroy
no funciona: https://github.com/rails/rails/issues/32376Dado que
throw(:abort)
se requiere Rails 5 para cancelar la ejecución: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chainprepend: true
es necesario para quedependent: :destroy
no se ejecute antes de que se ejecuten las validaciones: https://github.com/rails/rails/issues/3458Pueden pescar esto juntos a partir de otras respuestas y comentarios, pero no encontré ninguno de ellos completo.
Como nota al margen, muchos usaron una
has_many
relación como ejemplo en el que quieren asegurarse de no eliminar ningún registro si crearía registros huérfanos. Esto se puede resolver mucho más fácilmente:has_many :entities, dependent: :restrict_with_error
fuente
before_destroy :handle_destroy, prepend: true; before_destroy { throw(:abort) if errors.present? }
permitirá que los errores de otras validaciones before_destroy pasen en lugar de finalizar el proceso de destrucción de inmediatoTambién puede usar la devolución de llamada before_destroy para generar una excepción.
fuente
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 elimina una empresa, este proceso valida si hay productos asociados con empresas. Nota: debe escribir esto en la parte superior de la clase para validarlo primero.
fuente
Utilice la validación de contexto de ActiveRecord en 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
fuente
on: :destroy
, vea este problemaEsperaba que esto fuera compatible, así que abrí un problema de rieles para agregarlo:
https://github.com/rails/rails/issues/32376
fuente