test/paranoia_test.rb in paranoia-2.0.2 vs test/paranoia_test.rb in paranoia-2.0.3
- old
+ new
@@ -7,23 +7,40 @@
require 'test/unit'
Test::Unit::TestCase
end
require File.expand_path(File.dirname(__FILE__) + "/../lib/paranoia")
-ActiveRecord::Base.establish_connection :adapter => 'sqlite3', database: ':memory:'
-ActiveRecord::Base.connection.execute 'CREATE TABLE parent_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE featureful_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(32))'
-ActiveRecord::Base.connection.execute 'CREATE TABLE plain_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER NOT NULL, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)'
-ActiveRecord::Base.connection.execute 'CREATE TABLE non_paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER)'
+def connect!
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', database: ':memory:'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE parent_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, paranoid_model_with_has_one_id INTEGER)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_anthor_class_name_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, paranoid_model_with_has_one_id INTEGER)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_foreign_key_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, has_one_foreign_key_id INTEGER)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE featureful_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(32))'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE plain_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE fail_callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER NOT NULL, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE asplode_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE custom_sentinel_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME NOT NULL)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE non_paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER)'
+ ActiveRecord::Base.connection.execute 'CREATE TABLE idless_models (deleted_at DATETIME)'
+end
+class WithDifferentConnection < ActiveRecord::Base
+ establish_connection adapter: 'sqlite3', database: ':memory:'
+ connection.execute 'CREATE TABLE with_different_connections (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
+ acts_as_paranoid
+end
+
+connect!
+
class ParanoiaTest < test_framework
def setup
ActiveRecord::Base.connection.tables.each do |table|
ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
end
@@ -95,11 +112,11 @@
assert_equal nil, model.instance_variable_get(:@update_callback_called)
assert_equal nil, model.instance_variable_get(:@save_callback_called)
assert_equal nil, model.instance_variable_get(:@validate_called)
assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called)
- assert_equal nil, model.instance_variable_get(:@after_commit_callback_called)
+ assert model.instance_variable_get(:@after_commit_callback_called)
end
def test_destroy_behavior_for_paranoid_models
model = ParanoidModel.new
assert_equal 0, model.class.count
@@ -143,10 +160,40 @@
assert_equal 1, model.class.unscoped.count
assert_equal 1, model.class.only_deleted.count
assert_equal 1, model.class.deleted.count
end
+ def test_default_sentinel_value
+ assert_equal nil, ParanoidModel.paranoia_sentinel_value
+ end
+
+ def test_sentinel_value_for_custom_sentinel_models
+ model = CustomSentinelModel.new
+ assert_equal 0, model.class.count
+ model.save!
+ assert_equal DateTime.new(0), model.deleted_at
+ assert_equal 1, model.class.count
+ model.destroy
+
+ assert DateTime.new(0) != model.deleted_at
+ assert model.destroyed?
+
+ assert_equal 0, model.class.count
+ assert_equal 1, model.class.unscoped.count
+ assert_equal 1, model.class.only_deleted.count
+ assert_equal 1, model.class.deleted.count
+
+ model.restore
+ assert_equal DateTime.new(0), model.deleted_at
+ assert !model.destroyed?
+
+ assert_equal 1, model.class.count
+ assert_equal 1, model.class.unscoped.count
+ assert_equal 0, model.class.only_deleted.count
+ assert_equal 0, model.class.deleted.count
+ end
+
def test_destroy_behavior_for_featureful_paranoid_models
model = get_featureful_model
assert_equal 0, model.class.count
model.save!
assert_equal 1, model.class.count
@@ -244,20 +291,41 @@
model.reload
assert_equal false, model.destroyed?
end
+ def test_restore_on_object_return_self
+ model = ParanoidModel.create
+ model.destroy
+
+ assert_equal model.class, model.restore.class
+ end
+
# Regression test for #92
def test_destroy_twice
model = ParanoidModel.new
model.save
model.destroy
model.destroy
assert_equal 1, ParanoidModel.unscoped.where(id: model.id).count
end
+ def test_destroy_return_value_on_success
+ model = ParanoidModel.create
+ return_value = model.destroy
+
+ assert_equal(return_value, model)
+ end
+
+ def test_destroy_return_value_on_failure
+ model = FailCallbackModel.create
+ return_value = model.destroy
+
+ assert_equal(return_value, false)
+ end
+
def test_restore_behavior_for_callbacks
model = CallbackModel.new
model.save
id = model.id
model.destroy
@@ -269,17 +337,43 @@
model.reload
assert model.instance_variable_get(:@restore_callback_called)
end
- def test_real_destroy
+ def test_really_destroy
model = ParanoidModel.new
model.save
model.really_destroy!
refute ParanoidModel.unscoped.exists?(model.id)
end
+ def test_real_destroy_dependent_destroy
+ parent = ParentModel.create
+ child = parent.very_related_models.create
+ parent.really_destroy!
+ refute RelatedModel.unscoped.exists?(child.id)
+ end
+
+ def test_real_destroy_dependent_destroy_after_normal_destroy
+ parent = ParentModel.create
+ child = parent.very_related_models.create
+ parent.destroy
+ parent.really_destroy!
+ refute RelatedModel.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
+
+ parent_2 = ParentModel.create
+ child_2 = parent_2.very_related_models.create
+ parent_1.destroy
+ parent_1.really_destroy!
+ assert RelatedModel.unscoped.exists?(child_2.id)
+ end
+
if ActiveRecord::VERSION::STRING < "4.1"
def test_real_destroy
model = ParanoidModel.new
model.save
model.destroy!
@@ -348,10 +442,117 @@
assert_equal true, parent.reload.deleted_at.nil?
assert_equal true, first_child.reload.deleted_at.nil?
assert_equal true, second_child.destroyed?
end
+ # regression tests for #118
+ def test_restore_with_has_one_association
+ # setup and destroy test objects
+ hasOne = ParanoidModelWithHasOne.create
+ belongsTo = ParanoidModelWithBelong.create
+ anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
+ foreignKey = ParanoidModelWithForeignKeyBelong.create
+ hasOne.paranoid_model_with_belong = belongsTo
+ hasOne.class_name_belong = anthorClassName
+ hasOne.paranoid_model_with_foreign_key_belong = foreignKey
+ hasOne.save!
+
+ hasOne.destroy
+ assert_equal false, hasOne.deleted_at.nil?
+ assert_equal false, belongsTo.deleted_at.nil?
+
+ # Does it restore has_one associations?
+ hasOne.restore(:recursive => true)
+ hasOne.save!
+
+ assert_equal true, hasOne.reload.deleted_at.nil?
+ assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
+ assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
+ assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
+ assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
+ end
+
+ def test_new_restore_with_has_one_association
+ # setup and destroy test objects
+ hasOne = ParanoidModelWithHasOne.create
+ belongsTo = ParanoidModelWithBelong.create
+ anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
+ foreignKey = ParanoidModelWithForeignKeyBelong.create
+ hasOne.paranoid_model_with_belong = belongsTo
+ hasOne.class_name_belong = anthorClassName
+ hasOne.paranoid_model_with_foreign_key_belong = foreignKey
+ hasOne.save!
+
+ hasOne.destroy
+ assert_equal false, hasOne.deleted_at.nil?
+ assert_equal false, belongsTo.deleted_at.nil?
+
+ # Does it restore has_one associations?
+ newHasOne = ParanoidModelWithHasOne.with_deleted.find(hasOne.id)
+ newHasOne.restore(:recursive => true)
+ newHasOne.save!
+
+ assert_equal true, hasOne.reload.deleted_at.nil?
+ assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
+ assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
+ assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
+ assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
+ end
+
+ def test_model_restore_with_has_one_association
+ # setup and destroy test objects
+ hasOne = ParanoidModelWithHasOne.create
+ belongsTo = ParanoidModelWithBelong.create
+ anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
+ foreignKey = ParanoidModelWithForeignKeyBelong.create
+ hasOne.paranoid_model_with_belong = belongsTo
+ hasOne.class_name_belong = anthorClassName
+ hasOne.paranoid_model_with_foreign_key_belong = foreignKey
+ hasOne.save!
+
+ hasOne.destroy
+ assert_equal false, hasOne.deleted_at.nil?
+ assert_equal false, belongsTo.deleted_at.nil?
+
+ # Does it restore has_one associations?
+ ParanoidModelWithHasOne.restore(hasOne.id, :recursive => true)
+ hasOne.save!
+
+ assert_equal true, hasOne.reload.deleted_at.nil?
+ assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
+ assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
+ assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
+ assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
+ end
+
+ def test_restore_with_nil_has_one_association
+ # setup and destroy test object
+ hasOne = ParanoidModelWithHasOne.create
+ hasOne.destroy
+ assert_equal false, hasOne.reload.deleted_at.nil?
+
+ # Does it raise NoMethodException on restore of nil
+ hasOne.restore(:recursive => true)
+
+ assert hasOne.reload.deleted_at.nil?
+ end
+
+ # covers #131
+ def test_has_one_really_destroy_with_nil
+ model = ParanoidModelWithHasOne.create
+ model.really_destroy!
+
+ refute ParanoidModelWithBelong.unscoped.exists?(model.id)
+ end
+
+ def test_has_one_really_destroy_with_record
+ model = ParanoidModelWithHasOne.create { |record| record.build_paranoid_model_with_belong }
+ model.really_destroy!
+
+ refute ParanoidModelWithBelong.unscoped.exists?(model.id)
+ end
+
def test_observers_notified
a = ParanoidModelWithObservers.create
a.destroy
a.restore!
@@ -373,10 +574,61 @@
Ryan: "What should this method do?"
Sharon: "It should fix all the spelling errors on the page!"
}, output
end
+ def test_destroy_fails_if_callback_raises_exception
+ parent = AsplodeModel.create
+
+ assert_raises(StandardError) { parent.destroy }
+
+ #transaction should be rolled back, so parent NOT deleted
+ refute parent.destroyed?, 'Parent record was destroyed, even though AR callback threw exception'
+ end
+
+ def test_destroy_fails_if_association_callback_raises_exception
+ parent = ParentModel.create
+ children = []
+ 3.times { children << parent.asplode_models.create }
+
+ assert_raises(StandardError) { parent.destroy }
+
+ #transaction should be rolled back, so parent and children NOT deleted
+ refute parent.destroyed?, 'Parent record was destroyed, even though AR callback threw exception'
+ refute children.any?(&:destroyed?), 'Child record was destroyed, even though AR callback threw exception'
+ end
+
+ def test_restore_model_with_different_connection
+ ActiveRecord::Base.remove_connection # Disconnect the main connection
+ a = WithDifferentConnection.create
+ a.destroy!
+ a.restore!
+ # This test passes if no exception is raised
+ connect! # Reconnect the main connection
+ end
+
+ def test_restore_clear_association_cache_if_associations_present
+ parent = ParentModel.create
+ 3.times { parent.very_related_models.create }
+
+ parent.destroy
+
+ assert_equal 0, parent.very_related_models.count
+ assert_equal 0, parent.very_related_models.size
+
+ parent.restore(recursive: true)
+
+ assert_equal 3, parent.very_related_models.count
+ assert_equal 3, parent.very_related_models.size
+ end
+
+ def test_model_without_primary_key
+ assert_raises(RuntimeError) do
+ IdlessModel.class_eval{ acts_as_paranoid }
+ end
+ end
+
private
def get_featureful_model
FeaturefulModel.new(:name => "not empty")
end
end
@@ -386,10 +638,17 @@
class ParanoidModel < ActiveRecord::Base
belongs_to :parent_model
acts_as_paranoid
end
+class FailCallbackModel < ActiveRecord::Base
+ belongs_to :parent_model
+ acts_as_paranoid
+
+ before_destroy { |_| false }
+end
+
class FeaturefulModel < ActiveRecord::Base
acts_as_paranoid
validates :name, :presence => true, :uniqueness => true
end
@@ -417,10 +676,11 @@
acts_as_paranoid
has_many :paranoid_models
has_many :related_models
has_many :very_related_models, :class_name => 'RelatedModel', dependent: :destroy
has_many :non_paranoid_models, dependent: :destroy
+ has_many :asplode_models, dependent: :destroy
end
class RelatedModel < ActiveRecord::Base
acts_as_paranoid
belongs_to :parent_model
@@ -446,10 +706,14 @@
class CustomColumnModel < ActiveRecord::Base
acts_as_paranoid column: :destroyed_at
end
+class CustomSentinelModel < ActiveRecord::Base
+ acts_as_paranoid sentinel_value: DateTime.new(0)
+end
+
class NonParanoidModel < ActiveRecord::Base
end
class ParanoidModelWithObservers < ParanoidModel
def observers_notified
@@ -461,6 +725,46 @@
end
end
class ParanoidModelWithoutObservers < ParanoidModel
self.class.send(remove_method :notify_observers) if method_defined?(:notify_observers)
+end
+
+# refer back to regression test for #118
+class ParanoidModelWithHasOne < ParanoidModel
+ has_one :paranoid_model_with_belong, :dependent => :destroy
+ has_one :class_name_belong, :dependent => :destroy, :class_name => "ParanoidModelWithAnthorClassNameBelong"
+ has_one :paranoid_model_with_foreign_key_belong, :dependent => :destroy, :foreign_key => "has_one_foreign_key_id"
+end
+
+class ParanoidModelWithBelong < ActiveRecord::Base
+ acts_as_paranoid
+ belongs_to :paranoid_model_with_has_one
+end
+
+class ParanoidModelWithAnthorClassNameBelong < ActiveRecord::Base
+ acts_as_paranoid
+ belongs_to :paranoid_model_with_has_one
+end
+
+class ParanoidModelWithForeignKeyBelong < ActiveRecord::Base
+ acts_as_paranoid
+ belongs_to :paranoid_model_with_has_one
+end
+
+class FlaggedModel < PlainModel
+ acts_as_paranoid :flag_column => :is_deleted
+end
+
+class FlaggedModelWithCustomIndex < PlainModel
+ acts_as_paranoid :flag_column => :is_deleted, :indexed_column => :is_deleted
+end
+
+class AsplodeModel < ActiveRecord::Base
+ acts_as_paranoid
+ before_destroy do |r|
+ raise StandardError, 'ASPLODE!'
+ end
+end
+
+class IdlessModel < ActiveRecord::Base
end