Skip to content

Commit 2e30125

Browse files
committed
Fixed bug with really_destroy! after destroy and has_ones. Refactored logic in #restore_associated_records and leverage it for really_destroy.
1 parent 7c0d4ac commit 2e30125

File tree

2 files changed

+37
-33
lines changed

2 files changed

+37
-33
lines changed

lib/paranoia.rb

+23-29
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,7 @@ def really_destroy!
141141
end
142142
if dependent_reflections.any?
143143
dependent_reflections.each do |name, reflection|
144-
association_data = self.send(name)
145-
# has_one association can return nil
144+
association_data = self.send(name) || get_soft_deleted_has_one(reflection)
146145
# .paranoid? will work for both instances and classes
147146
next unless association_data && association_data.paranoid?
148147
if reflection.collection?
@@ -187,41 +186,36 @@ def restore_associated_records(recovery_window_range = nil)
187186
end
188187

189188
destroyed_associations.each do |association|
190-
association_data = send(association.name)
189+
association_data = send(association.name) || get_soft_deleted_has_one(association)
191190

192-
unless association_data.nil?
193-
if association_data.paranoid?
194-
if association.collection?
195-
association_data.only_deleted.each do |record|
196-
record.restore(:recursive => true, :recovery_window_range => recovery_window_range)
197-
end
198-
else
199-
association_data.restore(:recursive => true, :recovery_window_range => recovery_window_range)
191+
if association_data && association_data.paranoid?
192+
restore_options = { :recursive => true, :recovery_window_range => recovery_window_range }
193+
if association.collection?
194+
association_data.only_deleted.each do |record|
195+
record.restore(restore_options)
200196
end
201-
end
202-
end
203-
204-
if association_data.nil? && association.macro.to_s == "has_one"
205-
association_class_name = association.klass.name
206-
association_foreign_key = association.foreign_key
207-
208-
if association.type
209-
association_polymorphic_type = association.type
210-
association_find_conditions = { association_polymorphic_type => self.class.name.to_s, association_foreign_key => self.id }
211197
else
212-
association_find_conditions = { association_foreign_key => self.id }
213-
end
214-
215-
association_class = association_class_name.constantize
216-
if association_class.paranoid?
217-
association_class.only_deleted.where(association_find_conditions).first
218-
.try!(:restore, recursive: true, :recovery_window_range => recovery_window_range)
198+
association_data.restore(restore_options)
219199
end
220200
end
221201
end
222202

223203
clear_association_cache if destroyed_associations.present?
224204
end
205+
206+
# For soft deleted objects, has_one associations will return nil if the
207+
# associated object is also soft deleted. Because of this, we have to do the
208+
# object look-up explicitly. This method takes an association, and if it is
209+
# a has_one and the object referenced by the assocation uses paranoia, it
210+
# returns the object referenced by the association. Otherwise, it returns nil.
211+
def get_soft_deleted_has_one(association)
212+
return nil unless association.macro.to_s == "has_one"
213+
214+
association_find_conditions = { association.foreign_key => self.id }
215+
association_find_conditions[association.type] = self.class.name if association.type
216+
217+
association.klass.only_deleted.find_by(association_find_conditions) if association.klass.paranoid?
218+
end
225219
end
226220

227221
ActiveSupport.on_load(:active_record) do
@@ -301,7 +295,7 @@ def build_relation(klass, *args)
301295
class UniquenessValidator < ActiveModel::EachValidator
302296
prepend UniquenessParanoiaValidator
303297
end
304-
298+
305299
class AssociationNotSoftDestroyedValidator < ActiveModel::EachValidator
306300
def validate_each(record, attribute, value)
307301
# if association is soft destroyed, add an error

test/paranoia_test.rb

+14-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def setup!
4646
'active_column_models' => 'deleted_at DATETIME, active BOOLEAN',
4747
'active_column_model_with_uniqueness_validations' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN',
4848
'paranoid_model_with_belongs_to_active_column_model_with_has_many_relationships' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN, active_column_model_with_has_many_relationship_id INTEGER',
49-
'active_column_model_with_has_many_relationships' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN',
49+
'active_column_model_with_has_many_relationships' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN',
5050
'without_default_scope_models' => 'deleted_at DATETIME'
5151
}.each do |table_name, columns_as_sql_string|
5252
ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
@@ -189,11 +189,11 @@ def test_scoping_behavior_for_paranoid_models
189189
p2 = ParanoidModel.create(:parent_model => parent2)
190190
p1.destroy
191191
p2.destroy
192-
192+
193193
assert_equal 0, parent1.paranoid_models.count
194194
assert_equal 1, parent1.paranoid_models.only_deleted.count
195195

196-
assert_equal 2, ParanoidModel.only_deleted.joins(:parent_model).count
196+
assert_equal 2, ParanoidModel.only_deleted.joins(:parent_model).count
197197
assert_equal 1, parent1.paranoid_models.deleted.count
198198
assert_equal 0, parent1.paranoid_models.without_deleted.count
199199
p3 = ParanoidModel.create(:parent_model => parent1)
@@ -206,7 +206,7 @@ def test_only_deleted_with_joins
206206
c1 = ActiveColumnModelWithHasManyRelationship.create(name: 'Jacky')
207207
c2 = ActiveColumnModelWithHasManyRelationship.create(name: 'Thomas')
208208
p1 = ParanoidModelWithBelongsToActiveColumnModelWithHasManyRelationship.create(name: 'Hello', active_column_model_with_has_many_relationship: c1)
209-
209+
210210
c1.destroy
211211
assert_equal 1, ActiveColumnModelWithHasManyRelationship.count
212212
assert_equal 1, ActiveColumnModelWithHasManyRelationship.only_deleted.count
@@ -531,6 +531,15 @@ def test_real_destroy_dependent_destroy_after_normal_destroy
531531
refute RelatedModel.unscoped.exists?(child.id)
532532
end
533533

534+
def test_real_destroy_with_has_one_dependent_destroy_after_normal_destroy
535+
parent = ParentModel.create
536+
child = parent.paranoid_model = ParanoidModel.create
537+
parent.destroy
538+
parent.reload
539+
parent.really_destroy!
540+
refute ParanoidModel.unscoped.exists?(child.id)
541+
end
542+
534543
def test_real_destroy_dependent_destroy_after_normal_destroy_does_not_delete_other_children
535544
parent_1 = ParentModel.create
536545
child_1 = parent_1.very_related_models.create
@@ -1130,6 +1139,7 @@ class ParentModel < ActiveRecord::Base
11301139
has_one :non_paranoid_model, dependent: :destroy
11311140
has_many :asplode_models, dependent: :destroy
11321141
has_one :polymorphic_model, as: :parent, dependent: :destroy
1142+
has_one :paranoid_model, dependent: :destroy
11331143
end
11341144

11351145
class ParentModelWithCounterCacheColumn < ActiveRecord::Base

0 commit comments

Comments
 (0)