Bei der Zerstörung einer erholsamen Ressource möchte ich einige Dinge garantieren, bevor ich zulasse, dass eine Zerstörungsoperation fortgesetzt wird. Grundsätzlich möchte ich die Möglichkeit haben, den Zerstörungsvorgang zu stoppen, wenn ich feststelle, dass die Datenbank dadurch in einen ungültigen Zustand versetzt wird. Es gibt keine Validierungsrückrufe für eine Zerstörungsoperation. Wie "validiert" man also, ob eine Zerstörungsoperation akzeptiert werden soll?
ruby-on-rails
ruby
callback
Stephen Cagle
quelle
quelle
Antworten:
Sie können eine Ausnahme auslösen, die Sie dann abfangen. Rails-Wraps werden in einer Transaktion gelöscht, was hilfreich ist.
Zum Beispiel:
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
Alternativ können Sie den Rückruf before_destroy verwenden. Dieser Rückruf wird normalerweise verwendet, um abhängige Datensätze zu zerstören. Sie können jedoch eine Ausnahme auslösen oder stattdessen einen Fehler hinzufügen.
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
wird nun false zurückgeben undmyBooking.errors
wird bei der Rückgabe ausgefüllt.quelle
false
am Ende desbefore_destroy
nutzlos. Von nun an sollten Sie verwendenthrow(:abort)
(@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ).has_many :booking_payments, dependent: :restrict_with_error
nur eine Notiz:
Für Schienen 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
quelle
has_many :booking_payments, dependent: :restrict_with_error
Das habe ich mit Rails 5 gemacht:
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
quelle
has_many :qrcodes, dependent: :restrict_with_error
Die ActiveRecord-Zuordnungen has_many und has_one ermöglichen eine abhängige Option, mit der sichergestellt wird, dass verwandte Tabellenzeilen beim Löschen gelöscht werden. Dies dient jedoch normalerweise dazu, Ihre Datenbank sauber zu halten, anstatt zu verhindern, dass sie ungültig wird.
quelle
like_so
.dependent
Optionen, mit denen eine Entität nicht entfernt werden kann, wenn verwaiste Datensätze erstellt werden (dies ist für die Frage relevanter). ZBdependent: :restrict_with_error
Sie können die Zerstörungsaktion in eine "if" -Anweisung im Controller einschließen:
def destroy # in controller context if (model.valid_destroy?) model.destroy # if in model context, use `super` end end
Wo valid_destroy? ist eine Methode für Ihre Modellklasse, die true zurückgibt, wenn die Bedingungen zum Zerstören eines Datensatzes erfüllt sind.
Mit einer solchen Methode können Sie auch verhindern, dass dem Benutzer die Löschoption angezeigt wird. Dies verbessert die Benutzererfahrung, da der Benutzer keinen unzulässigen Vorgang ausführen kann.
quelle
Am Ende habe ich Code von hier verwendet, um eine can_destroy-Überschreibung für activerecord zu erstellen: 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
Dies hat den zusätzlichen Vorteil, dass es trivial ist, eine Löschtaste auf der Benutzeroberfläche auszublenden / anzuzeigen
quelle
Stand der Schienen 6:
Das funktioniert:
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
funktioniert nicht: https://github.com/rails/rails/issues/32376Da Rails 5
throw(:abort)
erforderlich ist, um die Ausführung abzubrechen: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chainprepend: true
ist erforderlich, damitdependent: :destroy
es nicht ausgeführt wird, bevor die Validierungen ausgeführt werden: https://github.com/rails/rails/issues/3458Sie können dies zusammen aus anderen Antworten und Kommentaren fischen, aber ich fand keine von ihnen vollständig.
Als Nebenbemerkung verwendeten viele eine
has_many
Beziehung als Beispiel, in der sie sicherstellen möchten, dass keine Datensätze gelöscht werden, wenn verwaiste Datensätze erstellt werden. Dies kann viel einfacher gelöst werden:has_many :entities, dependent: :restrict_with_error
quelle
Sie können auch den before_destroy-Rückruf verwenden, um eine Ausnahme auszulösen.
quelle
Ich habe diese Klassen oder Modelle
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
Wenn Sie nun ein Unternehmen löschen, überprüft dieser Prozess, ob Produkte mit Unternehmen verknüpft sind. Hinweis: Sie müssen dies oben in der Klasse schreiben, um es zuerst zu validieren.
quelle
Verwenden Sie die ActiveRecord-Kontextüberprüfung 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
quelle
on: :destroy
, siehe dieses ProblemIch hatte gehofft, dass dies unterstützt wird, also habe ich ein Rails-Problem geöffnet, um es hinzuzufügen:
https://github.com/rails/rails/issues/32376
quelle