Rails Basics - before_save and items pending destruction

This is a short post to discuss a "gotcha" you might encounter when using the before_save event with nested models that may have destroy pending.

In this example, we have posts and comments (a basic blog setup). We have decided to store the number of comments for each post, inside a field in the post model, to save on calculation time. Comments are saved as nested attributes of the post, we are using the before_save event to calculate the total any time the post is saved. Our code for the model is:

class Post < ActiveRecord::Base
  has_many :comments
  accepts_nested_attributes_for :comments, :allow_destroy => true
  before_save :calculate_total_comments

  def calculate_total_comments
    comments_total = 0
    self.comments.each do |c|
      comments_total += 1
    end
    self.total_comments = comments_total
  end
end

The problem with this code, is that if a comment is marked for destruction (as happens when you flag it with the destroy parameter), it will still be counted when you save the post, as it has not been destroyed yet. The destruction occurs after the beforesave is complete.

To resolve this, we use the marked_for_destruction? method.

The improved code looks like this:

class Post < ActiveRecord::Base
  has_many :comments
  accepts_nested_attributes_for :comments, :allow_destroy => true
  before_save :calculate_total_comments

  def calculate_total_comments
    comments_total = 0
    self.comments.each do |c|
      comments_total += 1 unless c.marked_for_destruction?
    end
    self.total_comments = comments_total
  end
end

By Joel Friedlaender