diff --git a/lib/paranoia.rb b/lib/paranoia.rb index 0b0d743a..63c67ac9 100644 --- a/lib/paranoia.rb +++ b/lib/paranoia.rb @@ -153,8 +153,7 @@ def really_destroy!(update_destroy_attributes: true) end if dependent_reflections.any? dependent_reflections.each do |name, reflection| - association_data = self.send(name) - # has_one association can return nil + association_data = self.send(name) || get_soft_deleted_has_one(reflection) # .paranoid? will work for both instances and classes next unless association_data && association_data.paranoid? if reflection.collection? @@ -201,17 +200,16 @@ def restore_associated_records(recovery_window_range = nil) end destroyed_associations.each do |association| - association_data = send(association.name) + association_data = send(association.name) || get_soft_deleted_has_one(association) - unless association_data.nil? - if association_data.paranoid? - if association.collection? - association_data.only_deleted.each do |record| - record.restore(:recursive => true, :recovery_window_range => recovery_window_range) - end - else - association_data.restore(:recursive => true, :recovery_window_range => recovery_window_range) + if association_data && association_data.paranoid? + restore_options = { :recursive => true, :recovery_window_range => recovery_window_range } + if association.collection? + association_data.only_deleted.each do |record| + record.restore(restore_options) end + else + association_data.restore(restore_options) end end @@ -235,6 +233,20 @@ def restore_associated_records(recovery_window_range = nil) clear_association_cache if destroyed_associations.present? end end + + # For soft deleted objects, has_one associations will return nil if the + # associated object is also soft deleted. Because of this, we have to do the + # object look-up explicitly. This method takes an association, and if it is + # a has_one and the object referenced by the assocation uses paranoia, it + # returns the object referenced by the association. Otherwise, it returns nil. + def get_soft_deleted_has_one(association) + return nil unless association.macro.to_s == "has_one" + + association_find_conditions = { association.foreign_key => self.id } + association_find_conditions[association.type] = self.class.name if association.type + + association.klass.only_deleted.find_by(association_find_conditions) if association.klass.paranoid? + end end ActiveSupport.on_load(:active_record) do diff --git a/test/paranoia_test.rb b/test/paranoia_test.rb index 86cc8748..332c0061 100644 --- a/test/paranoia_test.rb +++ b/test/paranoia_test.rb @@ -665,6 +665,15 @@ def test_real_destroy_dependent_destroy_after_normal_destroy refute RelatedModel.unscoped.exists?(child.id) end + def test_real_destroy_with_has_one_dependent_destroy_after_normal_destroy + parent = ParentModel.create + child = parent.paranoid_model = ParanoidModel.create + parent.destroy + parent.reload + parent.really_destroy! + refute ParanoidModel.unscoped.exists?(child.id) + end + def test_real_destroy_dependent_destroy_after_normal_destroy_does_not_delete_other_children parent_1 = ParentModel.create child_1 = parent_1.very_related_models.create @@ -1346,6 +1355,7 @@ class ParentModel < ActiveRecord::Base has_one :non_paranoid_model, dependent: :destroy has_many :asplode_models, dependent: :destroy has_one :polymorphic_model, as: :parent, dependent: :destroy + has_one :paranoid_model, dependent: :destroy before_destroy :validate_destroy def validate_destroy