Santiago Palladino

Santiago Palladino

How to check if object can be destroyed if it has dependent: restrict associations

3 min
Jan 31 2012
coding
3 min
Jan 31 2012

Rails provides several handy options for specifying how to deal with associated models upon deletion, for example:

class Blog
  has_many :posts, :dependent => :destroy
end

The destroy value for the dependent option will call the destroy method for every post in the blog when the blog itself is destroyed. Other options are delete or nullify, but the one we are interested in is restrict.

By specifying a relation as dependent restrict, Rails will prevent us from destroying a particular object if it has any associated objects. In the example, we would not be able to destroy a Blog if it has any Posts. It is implemented simply, by raising an ActiveRecord::DeleteRestrictionError if there is any associated object.

Now, this works perfectly for preventing us from accidentally destroying an object, but we will usually want to check if we can destroy it beforehand, this is, when rendering a page to the client with a big bad delete button. Showing a delete button only to show an alert box with a "Could not delete" annoying message is certainly not a good practice, we should simply not draw the delete button in the first place.

How do we check this? We could manually check if each and every one of the associations we have marked with dependent restrict in the object are empty, but taking Rails DRY principle into account, we would like to automatically get that information from the object.

Luckily, ActiveRecord provides reflection methods for obtaining info on the associations. Therefore, given an object, we can iterate its associations, and check if the restricted ones are empty or not.

This all boils down to this small method, which can be placed as an initializer in the Rails app:

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

That's it! Now you can simply make a small helper method that renders a destroy link if can_destroy?, or a plain span notifying the user why she cannot destroy the object.