Làm cách nào để tôi 'xác nhận' trên tiêu diệt trong đường ray


81

Khi phá hủy một tài nguyên còn lại, tôi muốn đảm bảo một số điều trước khi cho phép tiếp tục hoạt động phá hủy? Về cơ bản, tôi muốn khả năng dừng hoạt động phá hủy nếu tôi lưu ý rằng làm như vậy sẽ đặt cơ sở dữ liệu ở trạng thái không hợp lệ? Không có lệnh gọi lại xác thực nào đối với một hoạt động hủy, vậy làm cách nào để "xác thực" liệu một hoạt động hủy có được chấp nhận hay không?


Câu trả lời:


70

Bạn có thể nêu ra một ngoại lệ mà sau đó bạn bắt được. Rails kết thúc việc xóa trong một giao dịch, điều này giúp ích rất nhiều.

Ví dụ:

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

Ngoài ra, bạn có thể sử dụng lệnh gọi lại before_destroy. Lệnh gọi lại này thường được sử dụng để hủy các bản ghi phụ thuộc, nhưng thay vào đó bạn có thể đưa ra một ngoại lệ hoặc thêm lỗi.

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.destroybây giờ sẽ trả về false và myBooking.errorssẽ được điền vào ngày trở lại.


3
Lưu ý rằng hiện tại nó nói "... ok, hãy tiếp tục và tiêu diệt", bạn cần phải đặt "siêu", vì vậy phương thức hủy gốc thực sự được gọi.
Alexander Malfait

3
error.add_to_base không được chấp nhận trong Rails 3. Thay vào đó, bạn nên thực hiện error.add (: base, "message").
Ryan

9
Rails không xác thực trước khi hủy, vì vậy before_destroy sẽ cần trả về false để nó hủy hủy. Chỉ thêm lỗi là vô ích.
greywh

24
Với Rails 5, falsephần cuối của before_destroynó là vô dụng. Từ bây giờ bạn nên sử dụng throw(:abort)(@see: weblog.rubyonrails.org/2015/1/10/This-week-in-Rails/… ).
romainsalles

1
Ví dụ bạn bảo vệ chống lại các hồ sơ mồ côi có thể được giải quyết dễ dàng hơn nhiều quahas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

48

chỉ cần một lưu ý:

Đối với đường ray 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
Một vấn đề với cách tiếp cận này là lệnh gọi lại before_destroy dường như được gọi sau khi tất cả các booking_payments đã bị hủy.
chìm đắm vào

4
Vé liên quan: github.com/rails/rails/issues/3458 @sunkencity, bạn có thể khai báo before_destroy trước khi khai báo liên kết để tạm thời tránh điều này.
lulalala

1
Ví dụ bạn bảo vệ chống lại các hồ sơ mồ côi có thể được giải quyết dễ dàng hơn nhiều quahas_many :booking_payments, dependent: :restrict_with_error
thisismydesign

Theo hướng dẫn rails before_destroy gọi lại có thể và nên được đặt trước các liên kết với depend_destroy; điều này sẽ kích hoạt gọi lại trước khi các tiêu diệt liên quan được gọi là: guide.rubyonrails.org/…
grouchomc

20

Đó là những gì tôi đã làm với 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
Đây là một bài viết tốt đẹp mà giải thích hành vi này trong Rails 5: blog.bigbinary.com/2016/02/13/...
Yaro Holodiuk

1
Ví dụ bạn bảo vệ chống lại các hồ sơ mồ côi có thể được giải quyết dễ dàng hơn nhiều quahas_many :qrcodes, dependent: :restrict_with_error
thisismydesign

6

Các liên kết ActiveRecord has_many và has_one cho phép một tùy chọn phụ thuộc sẽ đảm bảo các hàng bảng liên quan bị xóa khi xóa, nhưng điều này thường là để giữ cho cơ sở dữ liệu của bạn sạch hơn là ngăn nó không hợp lệ.


1
Một cách khác để quan tâm đến dấu gạch dưới, nếu chúng là một phần của tên hàm hoặc tương tự, là bọc chúng trong dấu gạch ngược. Điều đó sẽ hiển thị sau đó dưới dạng mã , like_so.
Richard Jones

Cảm ơn bạn. Câu trả lời của bạn dẫn tôi đến một tìm kiếm về loại phụ thuộc tùy chọn đã được trả lời ở đây: stackoverflow.com/a/25962390/3681793
bonafernando

Ngoài ra còn có dependentcác tùy chọn không cho phép xóa một thực thể nếu nó sẽ tạo ra các bản ghi mồ côi (điều này phù hợp hơn với câu hỏi). Ví dụ:dependent: :restrict_with_error
thisismydesign

5

Bạn có thể kết thúc hành động hủy trong câu lệnh "if" trong bộ điều khiển:

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

Nơi valid_destroy? là một phương thức trên lớp mô hình của bạn trả về true nếu các điều kiện để hủy một bản ghi được đáp ứng.

Có một phương pháp như thế này cũng sẽ cho phép bạn ngăn việc hiển thị tùy chọn xóa cho người dùng - điều này sẽ cải thiện trải nghiệm người dùng vì người dùng sẽ không thể thực hiện một thao tác bất hợp pháp.


7
Vòng lặp vô hạn, bất cứ ai?
jenjenut233

1
bắt tốt, nhưng tôi đã giả định rằng phương pháp này nằm trong bộ điều khiển, trì hoãn mô hình. Nếu đó là trong mô hình chắc chắn sẽ gây ra vấn đề
Toby Hede

hehe, xin lỗi vì điều đó ... Tôi hiểu ý bạn, tôi vừa nhìn thấy "phương thức trên lớp mô hình của bạn" và nhanh chóng nghĩ "uh oh", nhưng bạn nói đúng - hãy phá hủy trên bộ điều khiển, điều đó sẽ hoạt động tốt. :)
jenjenut233

tất cả đều tốt, trên thực tế tốt hơn để được rất rõ ràng chứ không phải là làm cho cuộc sống một số người nghèo mới bắt đầu khó khăn với độ rõ nét nghèo
Toby Hede

1
Tôi cũng đã nghĩ đến việc làm điều đó trong Bộ điều khiển, nhưng nó thực sự thuộc về Mô hình để các đối tượng không thể bị phá hủy khỏi bảng điều khiển hoặc bất kỳ Bộ điều khiển nào khác có thể cần phải phá hủy các đối tượng đó. Giữ cho nó KHÔ. :)
Joshua Pinter

4

Tôi đã kết thúc bằng cách sử dụng mã từ đây để tạo ghi đè can_destroy trên 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

Điều này có thêm lợi ích là làm cho việc ẩn / hiện nút xóa trên ui trở nên đơn giản hơn


3

Tình trạng của Rails 6:

Những công việc này:

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: :destroykhông hoạt động: https://github.com/rails/rails/issues/32376

Vì Rails 5 throw(:abort)được yêu cầu hủy thực thi: https://makandracards.com/makandra/20301-cancelling-the-activerecord-callback-chain

prepend: truelà bắt buộc để dependent: :destroynó không chạy trước khi quá trình xác thực được thực thi: https://github.com/rails/rails/issues/3458

Bạn có thể kết hợp điều này với nhau từ các câu trả lời và nhận xét khác, nhưng tôi thấy không có câu trả lời nào là hoàn chỉnh.

Như một ghi chú bên lề, nhiều người đã sử dụng một has_manyquan hệ làm ví dụ mà họ muốn đảm bảo không xóa bất kỳ bản ghi nào nếu nó tạo ra các bản ghi không có. Điều này có thể được giải quyết dễ dàng hơn nhiều:

has_many :entities, dependent: :restrict_with_error



2

Tôi có các lớp học hoặc mô hình này

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

Bây giờ khi bạn xóa một doanh nghiệp, quy trình này sẽ xác thực nếu có các sản phẩm được liên kết với doanh nghiệp Lưu ý: Bạn phải viết điều này ở đầu lớp để xác thực nó trước.


1

Sử dụng xác thực ngữ cảnh ActiveRecord trong 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

Bạn có thể không validate on: :destroy, xem vấn đề này
thesecretmaster

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.