How to 'Validate' on Destroy in Rails

How do I 'validate' on destroy in rails

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.

Validate Before Destroy

In case somebody stumbles here looking for Rails 5, returning false is not the way anymore. Use throw(:abort) instead, like in Martin Cabrera Diaubalick's answer.



Before Rails 5

If you return false from that before_destroy method, it will prevent the destruction.

Rails 4 association validate destroy

From the rails documentation

Similar to the normal callbacks that hook into the life cycle of an
Active Record object, you can also define callbacks that get triggered
when you add an object to or remove an object from an association
collection.

Example from Docs

class Project
has_and_belongs_to_many :developers, after_add: :evaluate_velocity

def evaluate_velocity(developer)
...
end
end

So in your particular scenario try

  has_many :patients, through: :appointments, before_remove :check_remove

Validation not preventing destroy - Rails 5

This is a common problem which people run into with ActiveRecord.

The problem

The location_ids= setter method will immediately add/update/delete records, which is alluded to in the ActiveRecord Associations guide. That behavior often surprises developers and is often undesired. I almost always avoid it.

In your code you are first calling assign_attributes which in turn calls location_ids=. Changes are immediately persisted to the records. Subsequently, when save! is called, it opens a new transaction. If a validation error occurs then only the changes in that transaction will be rolled back, and those exclude the already-persisted changes made by location_ids=.

@customer.assign_attributes(params...)  # location_ids are saved outside of the `save!` transaction.
@customer.save! # validation errors will cause a rollback only to this point, excluding changes from the previous line.

An Easy Solution

Use update_attributes! to replace both assign_attributes and save!. This will have the effect of wrapping all changes in a transaction such that a rollback will undo everything like you want. Yay!

@customer.update_attributes!(params.require(:customer).permit(:first_name, :middle_initial, :last_name, :location_ids => []))

An Alternative Approach

Sometimes it might not be possible to avoid separate calls to assign_attributes and save. This makes things much more complicated. No option that I can think of for this case is trivial.

One possible solution is to use nested attributes to update/destroy the child records.

location_ids is really a shortcut for updating each Location record associated with the given Customer. Instead of relying on that you can use nested attributes in your forms to update the locations. This method can take advantage of the mark_for_destruction (link) feature for autosave. Describing a full solution for this approach would be prohibitively lengthy, but it has proven to be very effective for me.

Paperclip triggers validation error on destroy

Yes it is, use the :create and/or :update options with your custom validation.

You will do something like this:

validate :ratiocorrect, on: :create

By default, such validations will run every time you call valid? or save the object. But it is also possible to control when to run these custom validations by giving an :on option to the validate method, with either: :create or :update.

Check out Custom Methods for additional information.

Rails 4: validation prevents delete

you can do

validates :first_name, :last_name, :email, :password, presence: true, :on => [ :create, :update ]

Rails validates_associated on destroy

before_destroy {|obj| obj.part.validate_method }

Rails before_destroy error message accessible from parent

Error messages are generally assigned to the model object where they apply.

If you really want to, you could grab those errors and stick them in your address model's errors hash:

class Account < ActiveRecord::Base
before_destroy :check_for_destruction

def check_for_destruction
rejected = addresses.reject{|a| a.can_destroy?} # returns array of addresses that now have errors (they should return false)
rejected.each do |address|
address.errors.each do |e|
errors.add_to_base(e)
end
end
end
end

Something like that should work, assuming you have defined an Address#can_destroy? method.
(Disclaimer: code is untested, but should give you a good jumping off point)



Related Topics



Leave a reply



Submit