views:

531

answers:

4

I have a model which has many children. I was setting/removing the children as such:

mymodel.children_ids = [1,2,3]
mymodel.save #add the children
mymodel.children_ids = [1]
mymodel.save #remove children 2,3

This works just fine, but I just realized that none of the callbacks (i.e. after_destroy) are not being called on the children model.

After some digging, it turns out that the delete_all function is being executed, rather than destroy_all. As the docs correctly state, the delete_all function does not fire off the callbacks, so is there anyway to change this behavior?

Thanks.

+1  A: 

delete_all is being executed... just because? That's what the Rails core team thought should be called in that instance. However, you could explicitly call the destroy_all method on the model's children, but not using that type of query. It looks like you're setting the child IDs directly, not using any build() or destroy() methods.

Yes, you can overwrite its functionality. You could patch it and put it in /vendor, rewriting the same block of code with destroy_all instead. Then use a send command to replace the base ActiveRecord functionality with your own.

Jarrett Meyer
I suppose I could just monkey patch the piece of code that handles this.Any idea where that lives?
gmoniey
+1  A: 

Something like:

mymodel.children.find([2,3]).each {|c| c.destroy }

will do the job. It's not exactly what you wanted, but I hope it helps.

klew
I figured that I could just override the children_ids= function in the model class, and handle it there...but I was hoping there would be a cleaner/dryer way.
gmoniey
+1  A: 

For those interested, I added the following monkeypatch to force the has_many through to perform a destroy_all, rather than delete_all. There might be a better way, so I'm open to suggestions.

module ActiveRecord 
  module Associations
    class HasManyThroughAssociation < HasManyAssociation       
      def delete_records(records)
        klass = @reflection.through_reflection.klass
        records.each do |associate|
          klass.destroy_all(construct_join_attributes(associate)) #force auditing by using destroy_all rather than delete all
        end
      end
    end
  end
end
gmoniey
+1  A: 

I had similar problem with callbacks. Solved it using alias_method_chain to override default setter.

  def product_categories_with_destroy=(product_categories)
    unless new_record? 
      (self.product_categories - product_categories).each(&:destroy)
    end

    self.product_categories_without_destroy = product_categories
  end
  alias_method_chain :product_categories=, :destroy

More details on alias_method_chain:

http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/

Laurynas