레일에서 파괴시 '검증'하는 방법


81

안정된 리소스를 파괴 할 때 파괴 작업을 계속하기 전에 몇 가지 사항을 보장하고 싶습니다. 기본적으로 데이터베이스가 유효하지 않은 상태가 될 수 있음을 알면 삭제 작업을 중지 할 수있는 기능을 원합니까? 삭제 작업에는 유효성 검사 콜백이 없습니다. 그렇다면 삭제 작업을 수락해야하는지 여부를 어떻게 "확인"합니까?


답변:


70

그런 다음 예외를 발생시킬 수 있습니다. Rails는 트랜잭션에서 삭제를 래핑하여 문제를 해결합니다.

예를 들면 :

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

또는 before_destroy 콜백을 사용할 수 있습니다. 이 콜백은 일반적으로 종속 레코드를 삭제하는 데 사용되지만 예외를 발생 시키거나 대신 오류를 추가 할 수 있습니다.

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이제 false myBooking.errors를 반환하고 반환시 채워집니다.


3
지금 "... 좋아, 계속해서 파괴하라"라고 말하는 곳에 "super"를 넣어야한다. 그래서 원래 destroy 메소드가 실제로 호출된다.
Alexander Malfait

3
errors.add_to_base는 Rails 3에서 더 이상 사용되지 않습니다. 대신 errors.add (: base, "message")를 수행해야합니다.
라이언

9
Rails는 파괴 전에 유효성을 검사하지 않으므로 before_destroy가 파괴를 취소하려면 false를 반환해야합니다. 오류를 추가하는 것만으로는 쓸모가 없습니다.
graywh 2012

24
Rails 5를 사용하면 false의 끝에는 before_destroy쓸모가 없습니다. 이제부터는 throw(:abort)(@see : weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ) 를 사용해야합니다 .
romainsalles

1
분리 된 레코드 방어 귀하의 예를 통해 훨씬 더 쉽게 해결 될 수있다has_many :booking_payments, dependent: :restrict_with_error
thisismydesign

48

참고 :

레일 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
이 접근 방식의 문제점은 모든 booking_payments가 파괴 된 후에 before_destroy 콜백이 호출되는 것 같습니다 .
sunkencity

4
관련 티켓 : github.com/rails/rails/issues/3458 @sunkencity 당신은 이것을 일시적으로 피하기 위해 연관 선언 전에 before_destroy를 선언 할 수 있습니다.
lulalala 2013

1
분리 된 레코드 방어 귀하의 예를 통해 훨씬 더 쉽게 해결 될 수있다has_many :booking_payments, dependent: :restrict_with_error
thisismydesign

레일 가이드에 따라 before_destroy 콜백은 dependent_destroy와의 연결 전에 배치 할 수 있으며 배치해야합니다. 이것은 관련된 파괴가 호출되기 전에 콜백을 트리거합니다 : guides.rubyonrails.org/…
grouchomc

20

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

3
이 레일 (5)에서이 동작을 설명하는 좋은 기사입니다 : blog.bigbinary.com/2016/02/13/...
YARO Holodiuk

1
분리 된 레코드 방어 귀하의 예를 통해 훨씬 더 쉽게 해결 될 수있다has_many :qrcodes, dependent: :restrict_with_error
thisismydesign

6

ActiveRecord 연관 has_many 및 has_one은 관련 테이블 행이 삭제시 삭제되도록하는 종속 옵션을 허용하지만 이는 일반적으로 데이터베이스가 유효하지 않은 것을 방지하기보다는 깨끗하게 유지하기위한 것입니다.


1
밑줄을 처리하는 또 다른 방법은 밑줄이 함수 이름의 일부이거나 비슷한 경우 백틱으로 래핑하는 것입니다. 그러면 코드로 표시됩니다 like_so.
Richard Jones

감사합니다. 귀하의 답변은 여기에 답변 된 종속 옵션 유형에 대한 또 다른 검색으로 이어졌습니다 . stackoverflow.com/a/25962390/3681793
bonafernando 19

dependent분리 된 레코드를 생성 할 경우 엔터티 제거를 허용하지 않는 옵션 도 있습니다 (질문과 더 관련이 있음). 예dependent: :restrict_with_error
thisismydesign

5

컨트롤러의 "if"문에서 destroy 액션을 래핑 할 수 있습니다.

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

valid_destroy는 어디 입니까? 레코드 삭제 조건이 충족되면 true를 반환하는 모델 클래스의 메서드입니다.

이와 같은 방법을 사용하면 사용자에게 삭제 옵션이 표시되는 것을 방지 할 수 있습니다. 이는 사용자가 불법 작업을 수행 할 수 없기 때문에 사용자 경험을 향상시킬 것입니다.


7
무한 루프, 누구?
jenjenut233 2011 년

1
잘 잡았지만이 방법이 컨트롤러에 있다고 가정하고 모델을 연기했습니다. 그것이 모델에 있었다면 확실히 문제를 일으킬 것입니다
Toby Hede 2011 년

헤헤, 미안 해요 ... 무슨 말인지 알 겠어요. 방금 "모델 클래스에 대한 방법"을보고 빨리 "어 오"라고 생각했습니다.하지만 당신 말이 맞아요-컨트롤러에서 파괴하면 잘 작동합니다. :)
jenjenut233 2011 년

모든 좋은, 사실 더 나은 매우 메이크업 가난한 선명도와 형편없는 초보자의 생활이 어려운보다는 삭제합니다
토비 HEDE

1
컨트롤러에서도 해볼까 생각했지만 실제로는 모델에 속하므로 콘솔이나 다른 컨트롤러에서 개체를 파괴 할 필요가있을 수 있습니다. 건조하게 유지하십시오. :)
여호수아 핀터

4

여기에서 코드를 사용하여 activerecord에 can_destroy 재정의를 만들었습니다 : 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

이것은 UI에서 삭제 버튼을 숨기거나 표시하는 것을 간단하게 만드는 추가 이점이 있습니다.


4

Rails 6 현재 상태

이것은 작동합니다 :

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작동하지 않습니다 : https://github.com/rails/rails/issues/32376

throw(:abort)실행을 취소하려면 Rails 5 가 필요하므로 : https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: truedependent: :destroy유효성 검사가 실행되기 전에 실행되지 않도록 https://github.com/rails/rails/issues/3458 이 필요합니다 .

다른 답변과 의견에서 이것을 함께 낚시 할 수는 있지만 완전한 것은 없습니다.

참고로 많은 has_many사람들이 고아 레코드를 생성 할 경우 레코드를 삭제하지 않도록 하는 관계를 예로 사용했습니다 . 이것은 훨씬 더 쉽게 해결할 수 있습니다.

has_many :entities, dependent: :restrict_with_error


약간의 개선 사항 : before_destroy :handle_destroy, prepend: true; before_destroy { throw(:abort) if errors.present? }삭제 프로세스를 즉시 종료하는 대신 다른 before_destroy 유효성 검사의 오류를 통과 할 수 있습니다.
Paul Odeon


2

이 수업이나 모델이 있습니다.

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

이제 엔터프라이즈를 삭제할 때이 프로세스는 엔터프라이즈와 관련된 제품이 있는지 확인합니다. 참고 : 먼저 유효성을 검사하려면 클래스 맨 위에 이것을 작성해야합니다.


1

Rails 5에서 ActiveRecord 컨텍스트 유효성 검사를 사용합니다.

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

당신은하지 유효성을 검사 할 수 있습니다 on: :destroy, 볼 이 문제
thesecretmaster

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.