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