Search code examples
callbackruby-on-rails-4

before_destroy callback not stopping record from being deleted


I am trying to prevent a record from being destroyed if there are children.

class Submission < ActiveRecord::Base

has_many :quotations, :dependent => :destroy

 before_destroy :check_for_payments


  def quoted?
    quotations.any?
  end


  def has_payments?
   true if quotations.detect {|q| q.payment}
  end


  private

  def check_for_payments
    if quoted? && has_payments?
      errors[:base] << "cannot delete submission that has already been paid"
      false
    end
  end

end

class Quotation < ActiveRecord::Base

    #associations
    belongs_to :submission
        has_one :payment_notification   
        has_one :payment

         before_destroy :check_for_payments

private 

def check_for_payments
  if payment_notification || payment
    errors[:base] << "cannot delete quotation while payment exist"
    return false
  end
end
end

When I test this code the before_destroy :check_for_payments prevents the Quotation record from being deleted.

However the :check_for_payments in the Submission before_destroy callback does not stop the Submission from being deleted.

How can I stop the Submission with payments from being destroyed?


Solution

  • I would try the code below where I have:

    1. used a has_many :through association for payments
    2. avoided unnecessary record retrieval of quotations and payments by using any? without a block which results in using the association counter cache if defined, or the size of the association if already loaded and failing that an SQL COUNT if needed.
    3. avoided enumeration of quotations
    4. avoided testing for truthiness/presence of the q.payment association proxy directly which does not work for has_xxx. If you want to test for presence use q.payment.present?

    Try the following and see how you go:

    class Submission < ActiveRecord::Base
    
      has_many :quotations,
        inverse_of: :submission,
        dependent: :destroy
    
      has_many :payments,
        through: :quotations
    
      before_destroy :check_for_payments, prepend: true
    
    private
    
      def check_for_payments
        if payments.any?
          errors[:base] << "cannot delete submission that has already been paid"
          return false
        end
      end
    end