So there’s a Category that has many Expenses. The expenses for a category are destroyed when the category is destroyed. Elsewhere in my logic I would like to know if the category is removable based on whether it has any expenses and, if it does, whether any of the expenses it has are greater than zero.

class Category < ActiveRecord::Base
  has_many   :items, :dependent => :destroy

  def removable?
    self.expenses.find(:first, :conditions => ['total_cost > ?', 0], :select => 'id').nil?
  end

end

Should really get closer to the vest when protecting the data. So I added a callback to protect the category. Conveniently I can reuse the removable? method:

before_destroy :removable?

Let’s test it:

def test_should_destroy__when_removable
  assert_difference(Category, :count, -1) do
    @category.destroy
  end
end

Yay!

Loaded suite /Users/bjhess/Sites/.../test/unit/category_test
Started
.
Finished in 1.950119 seconds.

1 tests, 3 assertions, 0 failures, 0 errors

And the other test:

def test_should_not_destroy__when_not_removable
  create_expense(:total_cost => 50.00)
  assert_no_difference(Category, :count) do
    @category.destroy
  end
end

NO!

Loaded suite /Users/bjhess/Sites/.../test/unit/expense_category_test
Started
F

Finished in 1.965099 seconds.

  1) Failure:
test_should_not_destroy__when_not_removable(CategoryTest)
method assert_no_difference in test_helper.rb at line 58
method test_should_not_destroy__when_not_removable in category_test.rb at line 66
<8> expected but was
<7>.

1 tests, 2 assertions, 1 failures, 0 errors

What the crap? Apparently the conditional in my removable? method must be working within the transactional space of the destroy call. See, a category has many expenses, set with :dependent => :destroy. In terms of the transaction, all of my expenses have already been destroyed and the category is indeed removable.

If I were to work entirely in memory, things come out alright.

before_destroy :check_expenses_total

private

  def check_expenses_total
    self.expenses.inject(0){ |sum, expense| sum + expense.total_cost } <= 0
  end

So the lesson, I suppose, is be careful how DB-interactive you’re being in your destroy callbacks. Oh, and test.

(Another weird thing. self.expenses.sum(&:total_cost) didn’t seem to work. I had to use inject.)

Nov 28, 2007 · Subscribe · Archive · Projects · Twitter · GitHub · Flickr