Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recovery window option a la acts_as_paranoia #170

Open
wants to merge 10 commits into
base: rails4
Choose a base branch
from
24 changes: 19 additions & 5 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module Paranoia
@@default_sentinel_value = nil
@@default_dependent_recovery_window = 120

# Change default_sentinel_value in a rails initilizer
def self.default_sentinel_value=(val)
Expand All @@ -12,6 +13,10 @@ def self.default_sentinel_value
@@default_sentinel_value
end

def self.default_dependent_recovery_window
@@default_dependent_recovery_window
end

def self.included(klazz)
klazz.extend Query
klazz.extend Callbacks
Expand Down Expand Up @@ -87,16 +92,20 @@ def delete
end

def restore!(opts = {})
opts.merge!(:recovery_window => paranoia_dependent_recovery_window)
self.class.transaction do
run_callbacks(:restore) do
# Fixes a bug where the build would error because attributes were frozen.
# This only happened on Rails versions earlier than 4.1.
noop_if_frozen = ActiveRecord.version < Gem::Version.new("4.1")
deleted_at = send(paranoia_column)
if (noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen
write_attribute paranoia_column, paranoia_sentinel_value
update_column paranoia_column, paranoia_sentinel_value
end
restore_associated_records if opts[:recursive]
if opts[:recursive]
restore_associated_records(deleted_at, opts[:recovery_window])
end
end
end

Expand Down Expand Up @@ -125,7 +134,7 @@ def touch_paranoia_column

# restore associated records that have been soft deleted when
# we called #destroy
def restore_associated_records
def restore_associated_records(deleted_at, window)
destroyed_associations = self.class.reflect_on_all_associations.select do |association|
association.options[:dependent] == :destroy
end
Expand All @@ -136,9 +145,12 @@ def restore_associated_records
unless association_data.nil?
if association_data.paranoid?
if association.collection?
association_data.only_deleted.each { |record| record.restore(:recursive => true) }
x = association_data.only_deleted.
where("#{association.quoted_table_name}.#{paranoia_column} < ?", deleted_at + window).
where("#{association.quoted_table_name}.#{paranoia_column} > ?", deleted_at - window).
each { |record| record.restore(:recursive => true) }
else
association_data.restore(:recursive => true)
association_data.restore(:recursive => true, :recovery_window => window)
end
end
end
Expand Down Expand Up @@ -194,10 +206,12 @@ def really_destroy!
end

include Paranoia
class_attribute :paranoia_column, :paranoia_sentinel_value
class_attribute :paranoia_column, :paranoia_sentinel_value, :paranoia_dependent_recovery_window

self.paranoia_column = (options[:column] || :deleted_at).to_s
self.paranoia_sentinel_value = options.fetch(:sentinel_value) { Paranoia.default_sentinel_value }
self.paranoia_dependent_recovery_window = options[:dependent_recovery_window] || Paranoia.default_dependent_recovery_window

def self.paranoia_scope
where(paranoia_column => paranoia_sentinel_value)
end
Expand Down
44 changes: 44 additions & 0 deletions test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ def setup
end
end

def with_stubbed_current_time(value, &block)
metaclass = Time.instance_eval{ class << self; self; end }
metaclass.send(:alias_method, :__original_time_now, :now)
metaclass.send(:define_method, :now){ value }
begin
block.call
ensure
metaclass.send(:undef_method, :now)
metaclass.send(:alias_method, :now, :__original_time_now)
metaclass.send(:undef_method, :__original_time_now)
end
end

def test_plain_model_class_is_not_paranoid
assert_equal false, PlainModel.paranoid?
end
Expand Down Expand Up @@ -490,6 +503,7 @@ def test_restore_with_associations
parent = ParentModel.create
first_child = parent.very_related_models.create
second_child = parent.non_paranoid_models.create
third_child = parent.very_related_models.create

parent.destroy
assert_equal false, parent.deleted_at.nil?
Expand All @@ -512,6 +526,18 @@ def test_restore_with_associations
assert_equal true, parent.reload.deleted_at.nil?
assert_equal true, first_child.reload.deleted_at.nil?
assert_equal true, second_child.destroyed?

with_stubbed_current_time(Time.at(0)) do
first_child.destroy
end
with_stubbed_current_time(Time.at(3600)) do
parent.destroy
end
ParentModel.restore(parent.id, :recursive => true, :recovery_window => 5.minute)
assert_equal true, parent.reload.deleted_at.nil?
assert_equal false, first_child.reload.deleted_at.nil?
assert_equal true, second_child.destroyed?
assert_equal true, third_child.reload.deleted_at.nil?
end

# regression tests for #118
Expand Down Expand Up @@ -727,6 +753,19 @@ def test_restore_clear_association_cache_if_associations_present
assert_equal 3, parent.very_related_models.size
end

def test_restore_recursive_on_polymorphic_has_one_association
parent = ParentModel.create
polymorphic = PolymorphicModel.create(parent: parent)

parent.destroy

assert_equal 0, polymorphic.class.count

parent.restore(recursive: true)

assert_equal 1, polymorphic.class.count
end

def test_model_without_db_connection
ActiveRecord::Base.remove_connection

Expand Down Expand Up @@ -994,6 +1033,11 @@ class AsplodeModel < ActiveRecord::Base
end
end

class PolymorphicModel < ActiveRecord::Base
acts_as_paranoid
belongs_to :parent, polymorphic: true
end

class NoConnectionModel < ActiveRecord::Base
end

Expand Down