Raylarda yok etme durumunda nasıl 'doğrulayabilirim'


81

Huzurlu bir kaynağın imha edilmesi üzerine, bir yok etme operasyonunun devam etmesine izin vermeden önce birkaç şeyi garanti etmek istiyorum. Temel olarak, bunu yapmanın veritabanını geçersiz bir duruma getireceğini fark edersem yok etme işlemini durdurma yeteneğini istiyorum. İmha işleminde doğrulama geri araması yoktur, öyleyse bir yok etme işleminin kabul edilip edilmeyeceği nasıl "doğrulanır"?


Yanıtlar:


70

Daha sonra yakalayacağınız bir istisna yaratabilirsiniz. Rails, bir işlemde silmeleri sarmalar ve bu da önemli hale gelir.

Örneğin:

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

Alternatif olarak before_destroy geri aramayı da kullanabilirsiniz. Bu geri arama normalde bağımlı kayıtları yok etmek için kullanılır, ancak bunun yerine bir istisna atabilir veya bir hata ekleyebilirsiniz.

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şimdi yanlış döndürülecek ve myBooking.errorsiade edildiğinde doldurulacaktır.


3
Şimdi "... tamam, devam et ve yok et" yazan yere, "süper" koyman gerektiğine dikkat et, böylece orijinal yok etme yöntemi aslında çağrılır.
Alexander Malfait

3
error.add_to_base, Rails 3'te kullanımdan kaldırılmıştır. Bunun yerine errors.add (: base, "message") yapmalısınız.
Ryan

9
Rails yok etmeden önce doğrulama yapmaz, bu yüzden before_destroy'un yok etmeyi iptal etmesi için false döndürmesi gerekir. Sadece hata eklemek işe yaramaz.
graywh

24
Rails 5 ile, falsesonunda before_destroyişe yaramaz. Şu andan itibaren throw(:abort)(@bkz: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ) kullanmalısınız.
romainsalles

1
Yetim kayıtlara karşı savunma örneğiniz çok daha kolay bir şekilde çözülebilirhas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

48

sadece bir not:

Raylar için 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
Bu yaklaşımla ilgili bir sorun, before_destroy geri aramasının tüm booking_payments yok edildikten sonra çağrılıyor görünmesidir.
batıklık

4
İlgili bilet: github.com/rails/rails/issues/3458 @sunkencity, bunu geçici olarak önlemek için dernek bildiriminden önce before_destroy beyan edebilirsiniz.
lulalala

1
Yetim kayıtlara karşı savunma örneğiniz çok daha kolay bir şekilde çözülebilirhas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

Ray kılavuzuna göre before_destroy geri aramaları, bağımlı_destroy ile ilişkilendirmelerden önce yerleştirilebilir ve yerleştirilmelidir; bu, ilişkili yok etmeler
grouchomc

20

Rails 5 ile yaptığım şey buydu:

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

3
Bu, Rails 5'te bu davranışı açıklayan güzel bir makale: blog.bigbinary.com/2016/02/13/…
Yaro Holodiuk

1
Yetim kayıtlara karşı savunma örneğiniz çok daha kolay bir şekilde çözülebilirhas_many :qrcodes, dependent: :restrict_with_error
thisismydesign

6

ActiveRecord ilişkilendirmeleri has_many ve has_one, silme sırasında ilgili tablo satırlarının silinmesini sağlayacak bağımlı bir seçeneğe izin verir, ancak bu genellikle veritabanınızı geçersiz olmasını önlemek yerine temiz tutmak içindir.


1
Alt çizgilerle ilgilenmenin başka bir yolu, bir işlev adının veya benzerinin parçasıysa, onları ters işaretlere sarmaktır. Bu daha sonra kod olarak görüntülenecektir like_so.
Richard Jones

Teşekkür ederim. Cevabınız beni burada cevaplanan bağımlı seçenek türleri hakkında başka bir aramaya yönlendirdi : stackoverflow.com/a/25962390/3681793
bonafernando

dependentYetim kayıtlar oluşturacaksa, bir varlığın kaldırılmasına izin vermeyen seçenekler de vardır (bu, soruyla daha yakından ilgilidir). Örneğindependent: :restrict_with_error
thisismydesign

5

Yok etme eylemini denetleyicideki bir "if" ifadesine sarabilirsiniz:

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

Valid_destroy nerede ? model sınıfınızda bir kaydı yok etme koşulları karşılandığında true döndüren bir yöntemdir.

Bunun gibi bir yönteme sahip olmak, silme seçeneğinin kullanıcıya görüntülenmesini engellemenize de izin verir - bu, kullanıcı yasadışı bir işlem gerçekleştiremeyeceği için kullanıcı deneyimini iyileştirir.


1
iyi yakaladım, ancak bu yöntemin kontrolörde olduğunu varsayıyordum, modeli erteliyordum. Modelde olsaydı kesinlikle sorunlara neden olurdu
Toby Hede

hehe, bunun için üzgünüm ... Ne demek istediğini anlıyorum, az önce "model sınıfında yöntem" gördüm ve hızlıca "uh oh" diye düşündüm, ama haklısın - denetleyicide yok et, bu iyi çalışır. :)
jenjenut233

her şey iyi, aslında zayıf bir aceminin hayatını zayıf bir netlikle zorlaştırmaktansa çok net olmak daha iyidir
Toby Hede

1
Bunu Denetleyicide de yapmayı düşündüm, ancak gerçekten Modele ait olduğundan nesneler konsoldan veya bu nesneleri yok etmesi gerekebilecek herhangi bir Denetleyiciden yok edilemez. KURU tutun. :)
Joshua Pinter

Varlık hala kullanabilirsiniz, söz konusu ifdeyimi destroyyerine çağırmak dışında, Kontrolör eylem if model.valid_destroy?sadece çağrı, if model.destroyvb başarılı tahrip edip modeli kolu ve let
Joshua Pinter

5

Rails 6 itibariyle işlerin durumu:

Bu çalışıyor:

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çalışmıyor: https://github.com/rails/rails/issues/32376

Yürütmeyi throw(:abort)iptal etmek için Rails 5 gerektiğinden: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: truedependent: :destroydoğrulamalar yürütülmeden önce çalışmaması için gereklidir : https://github.com/rails/rails/issues/3458

Bunu diğer cevaplardan ve yorumlardan birlikte yakalayabilirsiniz, ancak hiçbirini eksiksiz bulmadım.

Bir yan not olarak, birçoğu, has_manyöksüz kayıtlar oluşturacaksa hiçbir kaydı silmemek için bir örnek olarak bir ilişki kullandı . Bu çok daha kolay çözülebilir:

has_many :entities, dependent: :restrict_with_error


Küçük bir iyileştirme: before_destroy :handle_destroy, prepend: true; before_destroy { throw(:abort) if errors.present? }Yok etme sürecini hemen sonlandırmak yerine, yok etme işleminden önceki diğer doğrulamalardan gelen hataların geçmesine izin verecek
Paul Odeon

4

Activerecord'da can_destroy geçersiz kılma oluşturmak için buradan kodu kullandım: 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

Bu, kullanıcı arabiriminde bir silme düğmesini gizlemeyi / göstermeyi önemsiz hale getirme avantajına sahiptir.



2

Bu sınıflara veya modellere sahibim

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

Artık bir işletmeyi sildiğinizde, bu işlem işletmelerle ilişkili ürünler olup olmadığını doğrular Not: Bunu önce doğrulamak için sınıfın en üstüne yazmanız gerekir.


1

Rails 5'te ActiveRecord bağlam doğrulamasını kullanın.

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

Doğrulayamazsınız on: :destroy, bu sayıya
thesecretmaster

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.