Activerecord-nested_attribute_destruction - check if accepts_nested_attributes associations were destroyed in the last save

TL;DR

class Harbor
  has_many :ships

  accepts_nested_attributes_for :ships, allow_destroy: true
end

harbor.ships_attributes = [harbor.ships.first.attributes.merge('_destroy': true)]
harbor.save!

harbor.ships_destroyed_during_save? # true

See that last line of code? That is what this gem adds :smiley:

The long version

On more than one occasion, I have found myself needing to know if a collection had an object that was destroyed via assignment of nested attributes. Unfortunately, I don’t believe ActiveRecord has a way do to that.

The root of the issue, as far as I have understood, is that the destroyed objects are removed from the associated collection. Because of that, I only know of two ways to check if there was a destruction during the save.

  1. Store the ids of the items in the collection before saving, and check if any of them do not exist after saving.
  2. Add a before_save callback that stores a list of items that are marked for destruction. After the save is committed, the list can be used to know if any items were destroyed.

Since there is a lot going on to properly implement #2, I decided to write a gem to handle it.

The gem is available at GitHub - brandoncc/activerecord-nested_attribute_destruction. I hope others find it useful, and I would love to receive any feedback that the community has.

Here is an example of usage:

class Lighthouse < ActiveRecord::Base
  belongs_to :harbor
end

class Ship < ActiveRecord::Base
  belongs_to :harbor
end

class Harbor < ActiveRecord::Base
  has_many :ships
  has_one  :lighthouse

  accepts_nested_attributes_for :ships, :lighthouse, allow_destroy: true
end

harbor = Harbor.create(name: 'Blue Lagoon')

harbor.ships << Ship.new
harbor.lighthouse = Lighthouse.new
harbor.save

harbor.ships_attributes = [harbor.ships.first.attributes.merge('_destroy': true)]
harbor.save

puts harbor.ships_destroyed_during_save?
# => true
puts harbor.lighthouse_destroyed_during_save?
# => false

harbor.lighthouse_attributes = harbor.lighthouse.attributes.merge('_destroy': true)
harbor.save

puts harbor.ships_destroyed_during_save?
# => false
puts harbor.lighthouse_destroyed_during_save?
# => true

Thanks for taking the time to read this. Happy holidays!