jchupp-is_paranoid 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 7
4
- :patch: 0
4
+ :patch: 1
@@ -1,33 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/models')
2
3
 
3
- class Person < ActiveRecord::Base #:nodoc:
4
- validates_uniqueness_of :name
5
- has_many :androids, :foreign_key => :owner_id, :dependent => :destroy
6
- end
7
-
8
- class Android < ActiveRecord::Base #:nodoc:
9
- validates_uniqueness_of :name
10
- has_many :components, :dependent => :destroy
11
-
12
- is_paranoid
13
-
14
- before_update :raise_hell
15
- def raise_hell
16
- raise "hell"
17
- end
18
- end
19
-
20
- class Component < ActiveRecord::Base #:nodoc:
21
- is_paranoid
22
- end
23
-
24
- class AndroidWithScopedUniqueness < ActiveRecord::Base #:nodoc:
25
- set_table_name :androids
26
- validates_uniqueness_of :name, :scope => :deleted_at
27
- is_paranoid
28
- end
29
-
30
- describe Android do
4
+ describe IsParanoid do
31
5
  before(:each) do
32
6
  Android.delete_all
33
7
  Person.delete_all
@@ -39,169 +13,194 @@ describe Android do
39
13
  @r2d2.components.create(:name => 'Rotors')
40
14
  end
41
15
 
42
- it "should delete normally" do
43
- Android.count_with_destroyed.should == 2
44
- Android.delete_all
45
- Android.count_with_destroyed.should == 0
46
- end
47
-
48
- it "should handle Model.destroy_all properly" do
49
- lambda{
50
- Android.destroy_all("owner_id = #{@luke.id}")
51
- }.should change(Android, :count).from(2).to(0)
52
- Android.count_with_destroyed.should == 2
53
- end
54
-
55
- it "should handle Model.destroy(id) properly without hitting update/save related callbacks" do
56
- lambda{
57
- Android.destroy(@r2d2.id)
58
- }.should change(Android, :count).from(2).to(1)
59
-
60
- Android.count_with_destroyed.should == 2
61
- end
62
-
63
- it "should be not show up in the relationship to the owner once deleted" do
64
- @luke.androids.size.should == 2
65
- @r2d2.destroy
66
- @luke.androids.size.should == 1
67
- Android.count.should == 1
68
- Android.first(:conditions => {:name => 'R2D2'}).should be_blank
69
- end
70
-
71
- it "should be able to find deleted items via find_with_destroyed" do
72
- @r2d2.destroy
73
- Android.find(:first, :conditions => {:name => 'R2D2'}).should be_blank
74
- Android.first_with_destroyed(:conditions => {:name => 'R2D2'}).should_not be_blank
75
- end
76
-
77
- it "should be able to find only deleted items via find_destroyed_only" do
78
- @r2d2.destroy
79
- Android.all_destroyed_only.size.should == 1
80
- Android.first_destroyed_only.should == @r2d2
81
- end
82
-
83
- it "should have a proper count inclusively and exclusively of deleted items" do
84
- @r2d2.destroy
85
- @c3p0.destroy
86
- Android.count.should == 0
87
- Android.count_with_destroyed.should == 2
88
- end
89
-
90
- it "should mark deleted on dependent destroys" do
91
- lambda{
92
- @luke.destroy
93
- }.should change(Android, :count).from(2).to(0)
94
- Android.count_with_destroyed.should == 2
95
- end
96
-
97
- it "should allow restoring without hitting update/save related callbacks" do
98
- @r2d2.destroy
99
- lambda{
100
- @r2d2.restore
101
- }.should change(Android, :count).from(1).to(2)
102
- end
103
-
104
- it "should restore dependent models when being restored" do
105
- @r2d2.destroy
106
- lambda{
107
- @r2d2.restore
108
- }.should change(Component, :count).from(0).to(1)
109
- end
110
-
111
- it "should allow the option to not restore dependent models when being restored" do
112
- @r2d2.destroy
113
- lambda{
114
- @r2d2.restore(:include_destroyed_dependents => false)
115
- }.should_not change(Component, :count)
116
- end
117
-
118
- it "should respond to various calculations" do
119
- @r2d2.destroy
120
- Android.sum('id').should == @c3p0.id
121
- Android.sum_with_destroyed('id').should == @r2d2.id + @c3p0.id
122
-
123
- Android.average_with_destroyed('id').should == (@r2d2.id + @c3p0.id) / 2.0
124
- end
125
-
126
- it "should not ignore deleted items in validation checks unless scoped" do
127
- # Androids are not validates_uniqueness_of scoped
128
- @r2d2.destroy
129
- lambda{
130
- Android.create!(:name => 'R2D2')
131
- }.should raise_error(ActiveRecord::RecordInvalid)
132
-
133
- lambda{
134
- # creating shouldn't raise an error
135
- another_r2d2 = AndroidWithScopedUniqueness.create!(:name => 'R2D2')
136
- # neither should destroying the second incarnation since the
137
- # validates_uniqueness_of is only applied on create
138
- another_r2d2.destroy
139
- }.should_not raise_error
140
- end
141
- end
142
-
143
- class Ninja < ActiveRecord::Base #:nodoc:
144
- validates_uniqueness_of :name, :scope => :visible
145
- is_paranoid :field => [:visible, false, true]
146
- end
147
-
148
- class Pirate < ActiveRecord::Base #:nodoc:
149
- is_paranoid :field => [:alive, false, true]
150
- end
151
-
152
- class DeadPirate < ActiveRecord::Base #:nodoc:
153
- set_table_name :pirates
154
- is_paranoid :field => [:alive, true, false]
155
- end
156
-
157
- class RandomPirate < ActiveRecord::Base #:nodoc:
158
- set_table_name :pirates
159
-
160
- def after_destroy
161
- raise 'after_destroy works'
162
- end
163
- end
164
-
165
- class UndestroyablePirate < ActiveRecord::Base #:nodoc:
166
- set_table_name :pirates
167
- is_paranoid :field => [:alive, false, true]
168
-
169
- def before_destroy
170
- false
171
- end
172
- end
173
-
174
- describe 'Ninjas and Pirates' do
175
- it "should allow specifying alternate fields and field values" do
176
- ninja = Ninja.create(:name => 'Esteban')
177
- ninja.destroy
178
- Ninja.first.should be_blank
179
- Ninja.find_with_destroyed(:first).should == ninja
180
-
181
- pirate = Pirate.create(:name => 'Reginald')
182
- pirate.destroy
183
- Pirate.first.should be_blank
184
- Pirate.find_with_destroyed(:first).should == pirate
185
-
186
- DeadPirate.first.id.should == pirate.id
187
- lambda{
188
- DeadPirate.first.destroy
189
- }.should change(Pirate, :count).from(0).to(1)
190
- end
16
+ describe 'destroying' do
17
+ it "should soft-delete a record" do
18
+ lambda{
19
+ Android.destroy(@r2d2.id)
20
+ }.should change(Android, :count).from(2).to(1)
21
+ Android.count_with_destroyed.should == 2
22
+ end
23
+
24
+ it "should not hit update/save related callbacks" do
25
+ lambda{
26
+ Android.first.update_attribute(:name, 'Robocop')
27
+ }.should raise_error
28
+
29
+ lambda{
30
+ Android.first.destroy
31
+ }.should_not raise_error
32
+ end
33
+
34
+ it "should soft-delete matching items on Model.destroy_all" do
35
+ lambda{
36
+ Android.destroy_all("owner_id = #{@luke.id}")
37
+ }.should change(Android, :count).from(2).to(0)
38
+ Android.count_with_destroyed.should == 2
39
+ end
40
+
41
+ describe 'related models' do
42
+ it "should no longer show up in the relationship to the owner" do
43
+ @luke.androids.size.should == 2
44
+ @r2d2.destroy
45
+ @luke.androids.size.should == 1
46
+ end
191
47
 
192
- it "should handle before_destroy and after_destroy callbacks properly" do
193
- edward = UndestroyablePirate.create(:name => 'Edward')
194
- lambda{
195
- edward.destroy
196
- }.should_not change(UndestroyablePirate, :count)
48
+ it "should soft-delete on dependent destroys" do
49
+ lambda{
50
+ @luke.destroy
51
+ }.should change(Android, :count).from(2).to(0)
52
+ Android.count_with_destroyed.should == 2
53
+ end
197
54
 
198
- raul = RandomPirate.create(:name => 'Raul')
199
- lambda{
200
- begin
55
+ end
56
+ end
57
+
58
+ describe 'finding destroyed models' do
59
+ it "should be able to find destroyed items via #find_with_destroyed" do
60
+ @r2d2.destroy
61
+ Android.find(:first, :conditions => {:name => 'R2D2'}).should be_blank
62
+ Android.first_with_destroyed(:conditions => {:name => 'R2D2'}).should_not be_blank
63
+ end
64
+
65
+ it "should be able to find only destroyed items via #find_destroyed_only" do
66
+ @r2d2.destroy
67
+ Android.all_destroyed_only.size.should == 1
68
+ Android.first_destroyed_only.should == @r2d2
69
+ end
70
+ end
71
+
72
+ describe 'calculations' do
73
+ it "should have a proper count inclusively and exclusively of destroyed items" do
74
+ @r2d2.destroy
75
+ @c3p0.destroy
76
+ Android.count.should == 0
77
+ Android.count_with_destroyed.should == 2
78
+ end
79
+
80
+ it "should respond to various calculations" do
81
+ @r2d2.destroy
82
+ Android.sum('id').should == @c3p0.id
83
+ Android.sum_with_destroyed('id').should == @r2d2.id + @c3p0.id
84
+ Android.average_with_destroyed('id').should == (@r2d2.id + @c3p0.id) / 2.0
85
+ end
86
+ end
87
+
88
+ describe 'deletion' do
89
+ it "should actually remove records on #delete_all" do
90
+ lambda{
91
+ Android.delete_all
92
+ }.should change(Android, :count_with_destroyed).from(2).to(0)
93
+ end
94
+
95
+ it "should actually remove records on #delete" do
96
+ lambda{
97
+ Android.first.delete
98
+ }.should change(Android, :count_with_destroyed).from(2).to(1)
99
+ end
100
+ end
101
+
102
+ describe 'restore' do
103
+ it "should allow restoring soft-deleted items" do
104
+ @r2d2.destroy
105
+ lambda{
106
+ @r2d2.restore
107
+ }.should change(Android, :count).from(1).to(2)
108
+ end
109
+
110
+ it "should not hit update/save related callbacks" do
111
+ @r2d2.destroy
112
+
113
+ lambda{
114
+ @r2d2.update_attribute(:name, 'Robocop')
115
+ }.should raise_error
116
+
117
+ lambda{
118
+ @r2d2.restore
119
+ }.should_not raise_error
120
+ end
121
+
122
+ it "should restore dependent models when being restored by default" do
123
+ @r2d2.destroy
124
+ lambda{
125
+ @r2d2.restore
126
+ }.should change(Component, :count).from(0).to(1)
127
+ end
128
+
129
+ it "should provide the option to not restore dependent models" do
130
+ @r2d2.destroy
131
+ lambda{
132
+ @r2d2.restore(:include_destroyed_dependents => false)
133
+ }.should_not change(Component, :count)
134
+ end
135
+ end
136
+
137
+ describe 'validations' do
138
+ it "should not ignore destroyed items in validation checks unless scoped" do
139
+ # Androids are not validates_uniqueness_of scoped
140
+ @r2d2.destroy
141
+ lambda{
142
+ Android.create!(:name => 'R2D2')
143
+ }.should raise_error(ActiveRecord::RecordInvalid)
144
+
145
+ lambda{
146
+ # creating shouldn't raise an error
147
+ another_r2d2 = AndroidWithScopedUniqueness.create!(:name => 'R2D2')
148
+ # neither should destroying the second incarnation since the
149
+ # validates_uniqueness_of is only applied on create
150
+ another_r2d2.destroy
151
+ }.should_not raise_error
152
+ end
153
+ end
154
+
155
+ describe 'alternate fields and field values' do
156
+ it "should properly function for boolean values" do
157
+ # ninjas are invisible by default. not being ninjas, we can only
158
+ # find those that are visible
159
+ ninja = Ninja.create(:name => 'Esteban', :visible => true)
160
+ ninja.vanish # aliased to destroy
161
+ Ninja.first.should be_blank
162
+ Ninja.find_with_destroyed(:first).should == ninja
163
+
164
+ # we're only interested in pirates who are alive by default
165
+ pirate = Pirate.create(:name => 'Reginald')
166
+ pirate.destroy
167
+ Pirate.first.should be_blank
168
+ Pirate.find_with_destroyed(:first).should == pirate
169
+
170
+ # we're only interested in pirates who are dead by default.
171
+ # zombie pirates ftw!
172
+ DeadPirate.first.id.should == pirate.id
173
+ lambda{
174
+ DeadPirate.first.destroy
175
+ }.should change(Pirate, :count).from(0).to(1)
176
+ end
177
+ end
178
+
179
+ describe 'after_destroy and before_destroy callbacks' do
180
+ it "should rollback if before_destroy fails" do
181
+ edward = UndestroyablePirate.create(:name => 'Edward')
182
+ lambda{
201
183
  edward.destroy
202
- rescue => ex
203
- ex.message.should == 'after_destroy works'
204
- end
205
- }.should_not change(RandomPirate, :count)
184
+ }.should_not change(UndestroyablePirate, :count)
185
+ end
186
+
187
+ it "should rollback if after_destroy raises an error" do
188
+ raul = RandomPirate.create(:name => 'Raul')
189
+ lambda{
190
+ begin
191
+ raul.destroy
192
+ rescue => ex
193
+ ex.message.should == 'after_destroy works'
194
+ end
195
+ }.should_not change(RandomPirate, :count)
196
+ end
197
+
198
+ it "should handle callbacks normally assuming no failures are encountered" do
199
+ component = Component.first
200
+ lambda{
201
+ component.destroy
202
+ }.should change(component, :name).to(Component::NEW_NAME)
203
+ end
204
+
206
205
  end
207
206
  end
data/spec/models.rb ADDED
@@ -0,0 +1,67 @@
1
+ class Person < ActiveRecord::Base #:nodoc:
2
+ validates_uniqueness_of :name
3
+ has_many :androids, :foreign_key => :owner_id, :dependent => :destroy
4
+ end
5
+
6
+ class Android < ActiveRecord::Base #:nodoc:
7
+ validates_uniqueness_of :name
8
+ has_many :components, :dependent => :destroy
9
+
10
+ is_paranoid
11
+
12
+ # this code is to ensure that our destroy and restore methods
13
+ # work without triggering before/after_update callbacks
14
+ before_update :raise_hell
15
+ def raise_hell
16
+ raise "hell"
17
+ end
18
+ end
19
+
20
+ class Component < ActiveRecord::Base #:nodoc:
21
+ is_paranoid
22
+ NEW_NAME = 'Something Else!'
23
+
24
+ after_destroy :change_name
25
+ def change_name
26
+ self.update_attribute(:name, NEW_NAME)
27
+ end
28
+ end
29
+
30
+ class AndroidWithScopedUniqueness < ActiveRecord::Base #:nodoc:
31
+ set_table_name :androids
32
+ validates_uniqueness_of :name, :scope => :deleted_at
33
+ is_paranoid
34
+ end
35
+
36
+ class Ninja < ActiveRecord::Base #:nodoc:
37
+ validates_uniqueness_of :name, :scope => :visible
38
+ is_paranoid :field => [:visible, false, true]
39
+
40
+ alias_method :vanish, :destroy
41
+ end
42
+
43
+ class Pirate < ActiveRecord::Base #:nodoc:
44
+ is_paranoid :field => [:alive, false, true]
45
+ end
46
+
47
+ class DeadPirate < ActiveRecord::Base #:nodoc:
48
+ set_table_name :pirates
49
+ is_paranoid :field => [:alive, true, false]
50
+ end
51
+
52
+ class RandomPirate < ActiveRecord::Base #:nodoc:
53
+ set_table_name :pirates
54
+
55
+ def after_destroy
56
+ raise 'after_destroy works'
57
+ end
58
+ end
59
+
60
+ class UndestroyablePirate < ActiveRecord::Base #:nodoc:
61
+ set_table_name :pirates
62
+ is_paranoid :field => [:alive, false, true]
63
+
64
+ def before_destroy
65
+ false
66
+ end
67
+ end
data/spec/schema.rb CHANGED
@@ -23,7 +23,7 @@ ActiveRecord::Schema.define(:version => 20090317164830) do
23
23
 
24
24
  create_table "ninjas", :force => true do |t|
25
25
  t.string "name"
26
- t.boolean "visible", :default => true
26
+ t.boolean "visible", :default => false
27
27
  end
28
28
 
29
29
  create_table "pirates", :force => true do |t|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jchupp-is_paranoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey Chupp
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-27 00:00:00 -07:00
12
+ date: 2009-05-02 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -27,6 +27,7 @@ files:
27
27
  - lib/is_paranoid.rb
28
28
  - spec/database.yml
29
29
  - spec/is_paranoid_spec.rb
30
+ - spec/models.rb
30
31
  - spec/schema.rb
31
32
  - spec/spec.opts
32
33
  - spec/spec_helper.rb