How do I 'validate' on destroy in rails

2019-01-06 12:52发布

On destruction of a restful resource, I want to guarantee a few things before I allow a destroy operation to continue? Basically, I want the ability to stop the destroy operation if I note that doing so would place the database in a invalid state? There are no validation callbacks on a destroy operation, so how does one "validate" whether a destroy operation should be accepted?

10条回答
我欲成王,谁敢阻挡
2楼-- · 2019-01-06 13:31

Use ActiveRecord context validation 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
查看更多
一纸荒年 Trace。
3楼-- · 2019-01-06 13:32

just a note:

For rails 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
查看更多
够拽才男人
4楼-- · 2019-01-06 13:32

You can wrap the destroy action in an "if" statement in the controller:

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

Where valid_destroy? is a method on your model class that returns true if the conditions for destroying a record are met.

Having a method like this will also let you prevent the display of the delete option to the user - which will improve the user experience as the user won't be able to perform an illegal operation.

查看更多
Summer. ? 凉城
5楼-- · 2019-01-06 13:37

You can raise an exception which you then catch. Rails wraps deletes in a transaction, which helps matters.

For example:

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

Alternatively you can use the before_destroy callback. This callback is normally used to destroy dependent records, but you can throw an exception or add an error instead.

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 will now return false, and myBooking.errors will be populated on return.

查看更多
Lonely孤独者°
6楼-- · 2019-01-06 13:38

The ActiveRecord associations has_many and has_one allows for a dependent option that will make sure related table rows are deleted on delete, but this is usually to keep your database clean rather than preventing it from being invalid.

查看更多
\"骚年 ilove
7楼-- · 2019-01-06 13:43

I ended up using code from here to create a can_destroy override on 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

This has the added benefit of making it trivial to hide/show a delete button on the ui

查看更多
登录 后发表回答