Czy w przypadku niszczenia spokojnego zasobu chcę zagwarantować kilka rzeczy, zanim pozwolę kontynuować operację niszczenia? Zasadniczo chcę mieć możliwość zatrzymania operacji niszczenia, jeśli zauważę, że spowodowałoby to nieprawidłowe ustawienie bazy danych? Nie ma żadnych wywołań zwrotnych walidacji operacji niszczenia, więc jak można „sprawdzić”, czy operacja zniszczenia powinna zostać zaakceptowana?
ruby-on-rails
ruby
callback
Stephen Cagle
źródło
źródło
Odpowiedzi:
Możesz zgłosić wyjątek, który następnie złapiesz. Railsy opakowują usunięcia w transakcję, co pomaga.
Na przykład:
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
Alternatywnie możesz użyć wywołania zwrotnego before_destroy. To wywołanie zwrotne jest zwykle używane do niszczenia rekordów zależnych, ale zamiast tego można zgłosić wyjątek lub dodać błąd.
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
zwróci teraz wartość false imyBooking.errors
zostanie wypełniony po powrocie.źródło
false
na końcubefore_destroy
jest bezużyteczny. Od teraz powinieneś używaćthrow(:abort)
(@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ).has_many :booking_payments, dependent: :restrict_with_error
tylko uwaga:
Do szyn 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
źródło
has_many :booking_payments, dependent: :restrict_with_error
Oto co zrobiłem z Railsami 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
źródło
has_many :qrcodes, dependent: :restrict_with_error
Skojarzenia ActiveRecord has_many i has_one pozwalają na zależną opcję, która zapewni, że powiązane wiersze tabeli zostaną usunięte podczas usuwania, ale zwykle ma to na celu utrzymanie bazy danych w czystości, a nie zapobieganie jej niepoprawności.
źródło
like_so
.dependent
opcje, które nie pozwalają na usunięcie podmiotu, jeśli spowodowałoby to utworzenie osieroconych rekordów (jest to bardziej istotne w przypadku pytania). Np.dependent: :restrict_with_error
Akcję zniszczenia można umieścić w instrukcji „if” w kontrolerze:
def destroy # in controller context if (model.valid_destroy?) model.destroy # if in model context, use `super` end end
Gdzie valid_destroy? jest metodą w klasie modelu, która zwraca wartość true, jeśli spełnione są warunki zniszczenia rekordu.
Posiadanie takiej metody pozwoli również zapobiec wyświetlaniu użytkownikowi opcji usuwania - co poprawi komfort użytkowania, ponieważ użytkownik nie będzie mógł wykonać nielegalnej operacji.
źródło
if
oświadczenia wdestroy
akcji kontrolera, z wyjątkiem zamiast wywoływaniaif model.valid_destroy?
, wystarczy zadzwonićif model.destroy
i niech model klamki czy zniszczyć był udany, itd.Stan na Rails 6:
To działa:
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
nie działa: https://github.com/rails/rails/issues/32376Ponieważ Rails 5
throw(:abort)
jest zobowiązany do anulowania wykonania: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chainprepend: true
jest wymagany, abydependent: :destroy
nie działał przed wykonaniem walidacji: https://github.com/rails/rails/issues/3458Możesz wyłowić to razem z innymi odpowiedziami i komentarzami, ale żadna z nich nie jest kompletna.
Na marginesie, wielu użyło
has_many
relacji jako przykładu, w którym chcą upewnić się, że nie usuwają żadnych rekordów, jeśli spowodowałoby to utworzenie osieroconych rekordów. Można to rozwiązać znacznie łatwiej:has_many :entities, dependent: :restrict_with_error
źródło
before_destroy :handle_destroy, prepend: true; before_destroy { throw(:abort) if errors.present? }
pozwoli na przejście błędów z innych walidacji before_destroy zamiast natychmiastowego kończenia procesu niszczeniaSkończyło się na tym, że użyłem kodu stąd, aby utworzyć zastąpienie can_destroy na 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
Ma to dodatkową zaletę polegającą na tym, że ukrywanie / wyświetlanie przycisku usuwania w interfejsie użytkownika jest trywialne
źródło
Możesz również użyć wywołania zwrotnego before_destroy, aby zgłosić wyjątek.
źródło
Mam te klasy lub modele
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
Teraz, gdy usuwasz przedsiębiorstwo, ten proces sprawdza, czy istnieją produkty powiązane z przedsiębiorstwami. Uwaga: Musisz wpisać to na początku klasy, aby najpierw je zweryfikować.
źródło
Użyj walidacji kontekstu ActiveRecord w Railsach 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
źródło
on: :destroy
, zobacz ten numerMiałem nadzieję, że to będzie obsługiwane, więc otworzyłem problem z szynami, aby go dodać:
https://github.com/rails/rails/issues/32376
źródło