Skip to content

Commit

Permalink
major improvements to clarity in specs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffrey Chupp committed May 2, 2009
1 parent 5a11d74 commit 7697099
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 190 deletions.
377 changes: 188 additions & 189 deletions spec/is_paranoid_spec.rb
@@ -1,33 +1,7 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/models')

class Person < ActiveRecord::Base #:nodoc:
validates_uniqueness_of :name
has_many :androids, :foreign_key => :owner_id, :dependent => :destroy
end

class Android < ActiveRecord::Base #:nodoc:
validates_uniqueness_of :name
has_many :components, :dependent => :destroy

is_paranoid

before_update :raise_hell
def raise_hell
raise "hell"
end
end

class Component < ActiveRecord::Base #:nodoc:
is_paranoid
end

class AndroidWithScopedUniqueness < ActiveRecord::Base #:nodoc:
set_table_name :androids
validates_uniqueness_of :name, :scope => :deleted_at
is_paranoid
end

describe Android do
describe IsParanoid do
before(:each) do
Android.delete_all
Person.delete_all
Expand All @@ -39,169 +13,194 @@ class AndroidWithScopedUniqueness < ActiveRecord::Base #:nodoc:
@r2d2.components.create(:name => 'Rotors')
end

it "should delete normally" do
Android.count_with_destroyed.should == 2
Android.delete_all
Android.count_with_destroyed.should == 0
end

it "should handle Model.destroy_all properly" do
lambda{
Android.destroy_all("owner_id = #{@luke.id}")
}.should change(Android, :count).from(2).to(0)
Android.count_with_destroyed.should == 2
end

it "should handle Model.destroy(id) properly without hitting update/save related callbacks" do
lambda{
Android.destroy(@r2d2.id)
}.should change(Android, :count).from(2).to(1)

Android.count_with_destroyed.should == 2
end

it "should be not show up in the relationship to the owner once deleted" do
@luke.androids.size.should == 2
@r2d2.destroy
@luke.androids.size.should == 1
Android.count.should == 1
Android.first(:conditions => {:name => 'R2D2'}).should be_blank
end

it "should be able to find deleted items via find_with_destroyed" do
@r2d2.destroy
Android.find(:first, :conditions => {:name => 'R2D2'}).should be_blank
Android.first_with_destroyed(:conditions => {:name => 'R2D2'}).should_not be_blank
end

it "should be able to find only deleted items via find_destroyed_only" do
@r2d2.destroy
Android.all_destroyed_only.size.should == 1
Android.first_destroyed_only.should == @r2d2
end

it "should have a proper count inclusively and exclusively of deleted items" do
@r2d2.destroy
@c3p0.destroy
Android.count.should == 0
Android.count_with_destroyed.should == 2
end

it "should mark deleted on dependent destroys" do
lambda{
@luke.destroy
}.should change(Android, :count).from(2).to(0)
Android.count_with_destroyed.should == 2
end

it "should allow restoring without hitting update/save related callbacks" do
@r2d2.destroy
lambda{
@r2d2.restore
}.should change(Android, :count).from(1).to(2)
end

it "should restore dependent models when being restored" do
@r2d2.destroy
lambda{
@r2d2.restore
}.should change(Component, :count).from(0).to(1)
end

it "should allow the option to not restore dependent models when being restored" do
@r2d2.destroy
lambda{
@r2d2.restore(:include_destroyed_dependents => false)
}.should_not change(Component, :count)
end

it "should respond to various calculations" do
@r2d2.destroy
Android.sum('id').should == @c3p0.id
Android.sum_with_destroyed('id').should == @r2d2.id + @c3p0.id

Android.average_with_destroyed('id').should == (@r2d2.id + @c3p0.id) / 2.0
end

it "should not ignore deleted items in validation checks unless scoped" do
# Androids are not validates_uniqueness_of scoped
@r2d2.destroy
lambda{
Android.create!(:name => 'R2D2')
}.should raise_error(ActiveRecord::RecordInvalid)

lambda{
# creating shouldn't raise an error
another_r2d2 = AndroidWithScopedUniqueness.create!(:name => 'R2D2')
# neither should destroying the second incarnation since the
# validates_uniqueness_of is only applied on create
another_r2d2.destroy
}.should_not raise_error
end
end

class Ninja < ActiveRecord::Base #:nodoc:
validates_uniqueness_of :name, :scope => :visible
is_paranoid :field => [:visible, false, true]
end

class Pirate < ActiveRecord::Base #:nodoc:
is_paranoid :field => [:alive, false, true]
end

class DeadPirate < ActiveRecord::Base #:nodoc:
set_table_name :pirates
is_paranoid :field => [:alive, true, false]
end

class RandomPirate < ActiveRecord::Base #:nodoc:
set_table_name :pirates

def after_destroy
raise 'after_destroy works'
end
end

class UndestroyablePirate < ActiveRecord::Base #:nodoc:
set_table_name :pirates
is_paranoid :field => [:alive, false, true]

def before_destroy
false
end
end

describe 'Ninjas and Pirates' do
it "should allow specifying alternate fields and field values" do
ninja = Ninja.create(:name => 'Esteban')
ninja.destroy
Ninja.first.should be_blank
Ninja.find_with_destroyed(:first).should == ninja

pirate = Pirate.create(:name => 'Reginald')
pirate.destroy
Pirate.first.should be_blank
Pirate.find_with_destroyed(:first).should == pirate

DeadPirate.first.id.should == pirate.id
lambda{
DeadPirate.first.destroy
}.should change(Pirate, :count).from(0).to(1)
end
describe 'destroying' do
it "should soft-delete a record" do
lambda{
Android.destroy(@r2d2.id)
}.should change(Android, :count).from(2).to(1)
Android.count_with_destroyed.should == 2
end

it "should not hit update/save related callbacks" do
lambda{
Android.first.update_attribute(:name, 'Robocop')
}.should raise_error

lambda{
Android.first.destroy
}.should_not raise_error
end

it "should soft-delete matching items on Model.destroy_all" do
lambda{
Android.destroy_all("owner_id = #{@luke.id}")
}.should change(Android, :count).from(2).to(0)
Android.count_with_destroyed.should == 2
end

describe 'related models' do
it "should no longer show up in the relationship to the owner" do
@luke.androids.size.should == 2
@r2d2.destroy
@luke.androids.size.should == 1
end

it "should handle before_destroy and after_destroy callbacks properly" do
edward = UndestroyablePirate.create(:name => 'Edward')
lambda{
edward.destroy
}.should_not change(UndestroyablePirate, :count)
it "should soft-delete on dependent destroys" do
lambda{
@luke.destroy
}.should change(Android, :count).from(2).to(0)
Android.count_with_destroyed.should == 2
end

raul = RandomPirate.create(:name => 'Raul')
lambda{
begin
end
end

describe 'finding destroyed models' do
it "should be able to find destroyed items via #find_with_destroyed" do
@r2d2.destroy
Android.find(:first, :conditions => {:name => 'R2D2'}).should be_blank
Android.first_with_destroyed(:conditions => {:name => 'R2D2'}).should_not be_blank
end

it "should be able to find only destroyed items via #find_destroyed_only" do
@r2d2.destroy
Android.all_destroyed_only.size.should == 1
Android.first_destroyed_only.should == @r2d2
end
end

describe 'calculations' do
it "should have a proper count inclusively and exclusively of destroyed items" do
@r2d2.destroy
@c3p0.destroy
Android.count.should == 0
Android.count_with_destroyed.should == 2
end

it "should respond to various calculations" do
@r2d2.destroy
Android.sum('id').should == @c3p0.id
Android.sum_with_destroyed('id').should == @r2d2.id + @c3p0.id
Android.average_with_destroyed('id').should == (@r2d2.id + @c3p0.id) / 2.0
end
end

describe 'deletion' do
it "should actually remove records on #delete_all" do
lambda{
Android.delete_all
}.should change(Android, :count_with_destroyed).from(2).to(0)
end

it "should actually remove records on #delete" do
lambda{
Android.first.delete
}.should change(Android, :count_with_destroyed).from(2).to(1)
end
end

describe 'restore' do
it "should allow restoring soft-deleted items" do
@r2d2.destroy
lambda{
@r2d2.restore
}.should change(Android, :count).from(1).to(2)
end

it "should not hit update/save related callbacks" do
@r2d2.destroy

lambda{
@r2d2.update_attribute(:name, 'Robocop')
}.should raise_error

lambda{
@r2d2.restore
}.should_not raise_error
end

it "should restore dependent models when being restored by default" do
@r2d2.destroy
lambda{
@r2d2.restore
}.should change(Component, :count).from(0).to(1)
end

it "should provide the option to not restore dependent models" do
@r2d2.destroy
lambda{
@r2d2.restore(:include_destroyed_dependents => false)
}.should_not change(Component, :count)
end
end

describe 'validations' do
it "should not ignore destroyed items in validation checks unless scoped" do
# Androids are not validates_uniqueness_of scoped
@r2d2.destroy
lambda{
Android.create!(:name => 'R2D2')
}.should raise_error(ActiveRecord::RecordInvalid)

lambda{
# creating shouldn't raise an error
another_r2d2 = AndroidWithScopedUniqueness.create!(:name => 'R2D2')
# neither should destroying the second incarnation since the
# validates_uniqueness_of is only applied on create
another_r2d2.destroy
}.should_not raise_error
end
end

describe 'alternate fields and field values' do
it "should properly function for boolean values" do
# ninjas are invisible by default. not being ninjas, we can only
# find those that are visible
ninja = Ninja.create(:name => 'Esteban', :visible => true)
ninja.vanish # aliased to destroy
Ninja.first.should be_blank
Ninja.find_with_destroyed(:first).should == ninja

# we're only interested in pirates who are alive by default
pirate = Pirate.create(:name => 'Reginald')
pirate.destroy
Pirate.first.should be_blank
Pirate.find_with_destroyed(:first).should == pirate

# we're only interested in pirates who are dead by default.
# zombie pirates ftw!
DeadPirate.first.id.should == pirate.id
lambda{
DeadPirate.first.destroy
}.should change(Pirate, :count).from(0).to(1)
end
end

describe 'after_destroy and before_destroy callbacks' do
it "should rollback if before_destroy fails" do
edward = UndestroyablePirate.create(:name => 'Edward')
lambda{
edward.destroy
rescue => ex
ex.message.should == 'after_destroy works'
end
}.should_not change(RandomPirate, :count)
}.should_not change(UndestroyablePirate, :count)
end

it "should rollback if after_destroy raises an error" do
raul = RandomPirate.create(:name => 'Raul')
lambda{
begin
raul.destroy
rescue => ex
ex.message.should == 'after_destroy works'
end
}.should_not change(RandomPirate, :count)
end

it "should handle callbacks normally assuming no failures are encountered" do
component = Component.first
lambda{
component.destroy
}.should change(component, :name).to(Component::NEW_NAME)
end

end
end

0 comments on commit 7697099

Please sign in to comment.