ruby-on-rails - example - rails after action
¿Cómo ''validaré'' la destrucción en rieles? (10)
Sobre la destrucción de un recurso relajante, quiero garantizar algunas cosas antes de permitir que continúe una operación de destrucción. 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?
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
Esperaba que esto fuera compatible, así que abrí un problema de rieles para que se agregara:
Las asociaciones de ActiveRecord has_many y has_one permiten una opción dependiente que garantizará 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.
Puede envolver la acción de destrucción en una instrucción "if" en el controlador:
def destroy # in controller context
if (model.valid_destroy?)
model.destroy # if in model context, use `super`
end
end
Donde 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 la visualización de la opción de eliminación para el usuario, lo que mejorará la experiencia del usuario ya que el usuario no podrá realizar una operación ilegal.
Puedes presentar una excepción que luego atrapes. 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 usar 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 devolverá false y myBooking.errors
se completará a la vuelta.
También puede usar la devolución de llamada before_destroy para generar una excepción.
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.
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 beneficio adicional de hacer que sea trivial ocultar / mostrar un botón de eliminar en la interfaz de usuario
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
solo una nota:
Para rieles 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