views:

132

answers:

1

I have a method which updates a count of dependent objects for a parent class whenever a dependent is created or destroyed. This generally works, but for some reason when a parent is deleted by a third class with :dependent => :destroy, I get a nil error for the parent object when the count method is called and thus nothing gets deleted. If I attempt to raise parent.inspect from the count method during this operation, it gets returned, so it clearly isn't nil. Any thoughts?

class DependentObject < ActiveRecord::Base
      belongs_to :parent
      belongs_to :third_object

      after_destroy :count

      def count
  count = DependentObject.count(:all, :conditions => ['parent_id = ?', self.parent_id])
  self.parent.count = count
  self.parent.save
 end

end

class Parent < ActiveRecord::Base
 belongs_to :third_object
 has_many :dependent_objects, :dependent => :delete_all
end

class ThirdObject
 has_many :parents, :dependent => :destroy
 has_many :dependent_objects, :dependent => :destroy

end

EDIT: The reason why I have :dependent => delete_all in the parent method was that I naively assumed that since :delete_all does not trigger :after_destroy and would only be called when a parent is destroyed, that it would avoid this problem. Looking at the development logs, this is indeed the case since it says 'DependentObject Delete all' for all the relevant objects before going through later and going back with 'DependentObject Destroy all' and encountering the nil error and initiating a rollback.

A: 

ActiveRecord likes caching used association objects. I think this might be where your problem is. When you do third_obj.dependent_objects the objects are loaded into memory and cached. Then when you destroy third_obj, it destroys parents then destroys dependent_objects. But if there are cached obj, rather than loading them from the db to destroy them, it calls destroy on the cached version, which has a stale parent id and already no longer exists in the db.

One way to fix this would be to reorder the has_many declarations so destroys are called in the other order.

    has_many :dependent_objects, :dependent => :destroy
    has_many :parents, :dependent => :destroy

This would have a few extra sql calls, but would not have nil problems. But there are better ways to count things. e.g.

parent.dependent_objects.count #does a sql count query--much faster
                               #than loading all the obj in memory

you could also use the :counter_cache => true option on parent's has_many :dependent_objects declaration and add a column to the parents table dependent_objects_count

BaroqueBobcat
This was absolutely ideal. Thanks so much!
Optimate
No problem. It was interesting to learn more about the way ActiveRecord works.
BaroqueBobcat