18

is there any way, given certain condition, cancel the destroy of an object on the before_destroy callback of active record? Thanks

Ronan Lopes
  • 3,320
  • 4
  • 25
  • 51

5 Answers5

32

You should return false.

Rails 5

"Canceling callbacks

If a before_* callback throws :abort, all the later callbacks and the associated action are cancelled."

Rails 4 and lower

"Canceling callbacks

If a before_* callback returns false, all the later callbacks and the associated action are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last."

Source

Community
  • 1
  • 1
Leonel Galán
  • 6,993
  • 2
  • 41
  • 60
  • 9
    With Rails 5, returning false no longer works. One should `throw(:abort)` to prevent record deletion. See [this comment](http://stackoverflow.com/questions/123078/how-do-i-validate-on-destroy-in-rails#comment59333149_123190) – RFVoltolini Aug 08 '16 at 15:16
  • 1
    Thanks for specifying the :abort technique for Rails 5. Should really help me out. –  Nov 28 '16 at 15:55
  • I've searched forever to find this answer - it's not even clear from the official documentation http://api.rubyonrails.org/classes/ActiveRecord/RecordNotDestroyed.html - thanks! – mtrolle Jun 09 '17 at 13:58
  • For rails 3, returning false just skips the other callbacks but it destroys the actual object. Anyone? – Balaji Radhakrishnan Nov 21 '18 at 03:11
10

As none of the given answers really solves the problem, but the comment above tells it - here the in form of an answer to make it easy to find:

In rails 5, instead of

before_destroy do
  if self.some_condition?
    return false
  end
end

use

before_destroy do
  if self.some_condition?
    throw(:abort)
  end
end

to make sure destroy is not being perfomed.

thanks to RFVoltolini's comment - this saved my day!

Florian Eck
  • 495
  • 3
  • 13
3

Rails wraps saves and destroys in a transaction, so a raise in the callback would work:

class Post < ActiveRecord::Base
  before_destroy :saveable?

  def saveable?
    if true
      raise "Destroy aborted; you can't do that!"
    end
  end
end

Substitute true for your condition.

Here's the abridged console output:

[1] pry(main)> Post.first.id
=> 1
[2] pry(main)> Post.first.destroy
RuntimeError: Destroy aborted; you can't do that!
[3] pry(main)> Post.first.id
=> 1

Documentation

Jake Worth
  • 5,490
  • 1
  • 25
  • 35
  • thnks, but don't want it raise anything... just do nothing – Ronan Lopes Jun 14 '16 at 20:21
  • Makes sense. Because this is a `before_*` callback, either `raise` or `false` works. The benefit of a `raise` is that you'll get a clear message you can trace in the logs, rather than the `destroy` quietly not happening. Which to choose depends on how unexpected your failure case may be. – Jake Worth Jun 14 '16 at 20:27
  • 1
    In Rails 5, you will have to explicitly `raise :abort`. You might as well start now. – mwoods79 Jun 15 '16 at 02:19
  • 2
    In Rails 5, `raise :abort` doesn't work for me. I think it *has* to be `throw :abort` – Maiko Kuppe Nov 25 '16 at 06:45
3

Returning false is the way to do it properly:

before_destroy do
  if self.some_condition?
    return false
  end
end

You can see the documentation here under point 6 Halting Execution. http://guides.rubyonrails.org/active_record_callbacks.html

James Milani
  • 1,921
  • 2
  • 16
  • 26
0

You can also override the #destroy method:

def destroy
  study_assignments.empty? ? super : raise("can not be destroyed")
end
Kris
  • 19,188
  • 9
  • 91
  • 111