Search code examples
ruby-on-railsruby-on-rails-3activerecordhas-and-belongs-to-many

How to prevent has_and_belongs_to_many delete option from deleting records that are being used in Rails?


class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end

In console:

part1 = Part.new
assembly1 = Assembly.new
assembly1.parts << part1
part1.delete
Parts.all
 => []

Checking assembly1.parts shows that there is still a relationship.(!)

How is this possible when the record was deleted?

Also, how to prevent deletion of parts that are associated to assemblies?

Working in Rails 3.0.7.


Solution

  • Everything you were doing here was done from memory (nothing was stored in the database).

    the ActiveRecord delete method will remove an object from the database but it doesn't look for other objects in memory that may have already been referencing that object. I think if you did assembly1.parts.delete(part1) that would likely do what you were expecting.

    If you had saved the objects to the database:

    part1 = Part.create
    assembly1 = Assembly.create(:parts => [part1])
    assembly1.parts
    # => [part1]
    part1.delete
    assembly1.parts
    # => [part1]
    assembly1.reload
    assembly1.parts
    # => []
    

    Note here how even if it's in the database part1.delete won't necessarily remove it from your assembly object until you refresh the in-memory collection or delete it using the method I mentioned earlier assembly1.parts.delete(part1)

    UPDATE

    I think you usually shouldn't use the delete() method. You should almost always use destroy(). delete() will just fire off a delete to the database and ignores all callbacks and I believe :dependent => :destroy-style declarations in your model. If you use the destroy() method then you can declare a before_destroy callback in your model:

    class MyClass
      has_and_belongs_to_many :foos
    
      before_destroy :allow_destroy
    
      def allow_destroy
        foos.empty?
      end
    end
    

    That should get your requirement of not destroying it if it is part of an assembly. You cannot stop delete() from executing because it ignores callbacks: ActiveRecord::Relation#delete documentation

    More info about model callbacks (documentation)