Permalink
Browse files

major improvements to clarity in specs

  • Loading branch information...
1 parent 5a11d74 commit 76970996219fe89ddb7dace5f23057bbf5893681 Jeffrey Chupp committed May 2, 2009
Showing with 189 additions and 190 deletions.
  1. +188 −189 spec/is_paranoid_spec.rb
  2. +1 −1 spec/schema.rb
View
@@ -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
@@ -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
Oops, something went wrong.

0 comments on commit 7697099

Please sign in to comment.