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 +1 -1
- data/spec/is_paranoid_spec.rb +188 -189
- data/spec/models.rb +67 -0
- data/spec/schema.rb +1 -1
- metadata +3 -2
data/VERSION.yml
CHANGED
data/spec/is_paranoid_spec.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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 =>
|
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.
|
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-
|
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
|