soft_destroyable 0.1.8 → 0.5.0
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/README.textile +9 -2
- data/VERSION +1 -1
- data/lib/soft_destroyable.rb +26 -13
- data/lib/soft_destroyable/table_definition.rb +6 -0
- data/soft_destroyable.gemspec +1 -1
- data/test/class_method_test.rb +4 -2
- data/test/dependent_delete_all_test.rb +22 -0
- data/test/dependent_delete_test.rb +18 -0
- data/test/dependent_destroy_test.rb +29 -0
- data/test/dependent_nullify_test.rb +35 -0
- data/test/dependent_restrict_test.rb +46 -1
- data/test/test_helper.rb +15 -0
- metadata +3 -3
data/README.textile
CHANGED
@@ -13,9 +13,12 @@ h3. Details
|
|
13
13
|
|
14
14
|
This changes the behavior of the +destroy+ method to become a soft-destroy, which
|
15
15
|
will set the +deleted_at+ attribute to <tt>Time.now</tt>, and the +deleted+ attribute to <tt>true</tt>
|
16
|
-
It exposes the +revive+ method to reverse the effects of +destroy
|
16
|
+
It exposes the +revive+ method to reverse the effects of +destroy+ (for :dependent => :destroy associations only).
|
17
17
|
It also exposes the +destroy!+ method which can be used to <b>really</b> destroy an object and it's associations.
|
18
18
|
|
19
|
+
+revive+ will not revive child associations which have been destroyed by actions other a destroy of the parent.
|
20
|
+
This requires the column attribute +revive_with_parent+.
|
21
|
+
|
19
22
|
Standard ActiveRecord destroy callbacks are _not_ called, however you can override +before_soft_destroy+, +after_soft_destroy+,
|
20
23
|
and +before_destroy!+ on your soft_destroyable models.
|
21
24
|
|
@@ -83,6 +86,10 @@ h3. Note on Patches/Pull Requests
|
|
83
86
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
84
87
|
* Send me a pull request
|
85
88
|
|
86
|
-
h3.
|
89
|
+
h3. Contributors
|
90
|
+
|
91
|
+
* "Mack Talcott":https://github.com/mtalcott
|
92
|
+
|
93
|
+
h3. Copyright
|
87
94
|
|
88
95
|
Copyright (c) 2010-11 Michael Kintzer, released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/soft_destroyable.rb
CHANGED
@@ -5,9 +5,12 @@ require "#{File.dirname(__FILE__)}/soft_destroyable/is_soft_destroyable"
|
|
5
5
|
#
|
6
6
|
# This changes the behavior of the +destroy+ method to being a soft-destroy, which
|
7
7
|
# will set the +deleted_at+ attribute to <tt>Time.now</tt>, and the +deleted+ attribute to <tt>true</tt>
|
8
|
-
# It exposes the +revive+ method to reverse the effects of +destroy
|
8
|
+
# It exposes the +revive+ method to reverse the effects of +destroy+ (for :dependent => :destroy associations only).
|
9
9
|
# It also exposes the +destroy!+ method which can be used to <b>really</b> destroy an object and it's associations.
|
10
10
|
#
|
11
|
+
# +revive+ will not cascade revive child associations which have been destroyed by actions other a destroy of the parent.
|
12
|
+
# This requires the column attribute +revive_with_parent+.
|
13
|
+
#
|
11
14
|
# Standard ActiveRecord destroy callbacks are _not_ called, however you can override +before_soft_destroy+, +after_soft_destroy+,
|
12
15
|
# and +before_destroy!+ on your soft_destroyable models.
|
13
16
|
#
|
@@ -109,7 +112,9 @@ module SoftDestroyable
|
|
109
112
|
def revive
|
110
113
|
transaction do
|
111
114
|
cascade_revive
|
112
|
-
|
115
|
+
update_hash = {:deleted_at => nil, :deleted => false}
|
116
|
+
update_hash[:revive_with_parent] = true if respond_to?(:revive_with_parent)
|
117
|
+
update_attributes(update_hash)
|
113
118
|
end
|
114
119
|
end
|
115
120
|
|
@@ -153,7 +158,13 @@ module SoftDestroyable
|
|
153
158
|
cascade_to_soft_dependents { |assoc_obj|
|
154
159
|
if assoc_obj.respond_to?(:destroy) && assoc_obj.respond_to?(:revive)
|
155
160
|
wrap_with_callbacks(assoc_obj, "soft_destroy") do
|
156
|
-
assoc_obj.
|
161
|
+
if assoc_obj.deleted? && assoc_obj.respond_to?(:revive_with_parent?)
|
162
|
+
# if assoc_obj already deleted, and we are cascading a delete, just update the attribute revive_with_parent to false
|
163
|
+
# so we know not to revive this object on a cascade of revive
|
164
|
+
assoc_obj.update_attributes(:revive_with_parent => false)
|
165
|
+
else
|
166
|
+
assoc_obj.destroy
|
167
|
+
end
|
157
168
|
end
|
158
169
|
else
|
159
170
|
wrap_with_callbacks(assoc_obj, "soft_destroy") do
|
@@ -179,30 +190,32 @@ module SoftDestroyable
|
|
179
190
|
end
|
180
191
|
|
181
192
|
def cascade_revive
|
182
|
-
cascade_to_soft_dependents { |assoc_obj|
|
183
|
-
assoc_obj.revive if assoc_obj.respond_to?(:revive)
|
193
|
+
cascade_to_soft_dependents(true) { |assoc_obj|
|
194
|
+
assoc_obj.revive if assoc_obj.respond_to?(:revive) && (!assoc_obj.respond_to?(:revive_with_parent?) ||
|
195
|
+
assoc_obj.revive_with_parent?)
|
184
196
|
}
|
185
197
|
end
|
186
198
|
|
187
|
-
|
199
|
+
# cascade of revive only applied to :dependent => :destroy associations
|
200
|
+
def cascade_to_soft_dependents(reviving = false, &block)
|
188
201
|
return unless block_given?
|
189
202
|
|
190
203
|
# fail fast on :dependent => :restrict
|
191
|
-
restrict_dependencies.each { |assoc_sym| handle_restrict(assoc_sym) }
|
204
|
+
restrict_dependencies.each { |assoc_sym| handle_restrict(assoc_sym) } unless reviving
|
192
205
|
|
193
206
|
non_restrict_dependencies.each do |assoc_sym|
|
194
|
-
reflection
|
207
|
+
reflection = reflection_for(assoc_sym)
|
195
208
|
association = send(reflection.name)
|
196
209
|
|
197
210
|
case reflection.options[:dependent]
|
198
211
|
when :destroy
|
199
212
|
handle_destroy(reflection, association, &block)
|
200
213
|
when :nullify
|
201
|
-
handle_nullify(reflection, association)
|
214
|
+
handle_nullify(reflection, association) unless reviving
|
202
215
|
when :delete_all
|
203
|
-
handle_delete_all(reflection, association)
|
216
|
+
handle_delete_all(reflection, association) unless reviving
|
204
217
|
when :delete
|
205
|
-
handle_delete(reflection, association)
|
218
|
+
handle_delete(reflection, association) unless reviving
|
206
219
|
else
|
207
220
|
end
|
208
221
|
|
@@ -223,7 +236,7 @@ module SoftDestroyable
|
|
223
236
|
end
|
224
237
|
|
225
238
|
def handle_restrict(assoc_sym)
|
226
|
-
reflection
|
239
|
+
reflection = reflection_for(assoc_sym)
|
227
240
|
association = send(reflection.name)
|
228
241
|
case reflection.macro
|
229
242
|
when :has_many
|
@@ -278,7 +291,7 @@ module SoftDestroyable
|
|
278
291
|
|
279
292
|
def restrict_on_non_empty_has_many(reflection, association)
|
280
293
|
return unless association
|
281
|
-
association.each {|assoc_obj|
|
294
|
+
association.each { |assoc_obj|
|
282
295
|
if assoc_obj.respond_to?(:deleted?)
|
283
296
|
raise ActiveRecord::DeleteRestrictionError.new(reflection) if !assoc_obj.deleted?
|
284
297
|
else
|
@@ -8,6 +8,12 @@ module SoftDestroyable
|
|
8
8
|
def soft_destroyable
|
9
9
|
column "deleted", :boolean, :default => false
|
10
10
|
column "deleted_at", :datetime
|
11
|
+
column "revive_with_parent", :boolean, :default => true
|
12
|
+
end
|
13
|
+
|
14
|
+
# useful on migration's for tables which were migrated with 'soft_destroyable' prior to revive_with_parent
|
15
|
+
def revive_tracking
|
16
|
+
column "revive_with_parent", :boolean, :default => true
|
11
17
|
end
|
12
18
|
|
13
19
|
end
|
data/soft_destroyable.gemspec
CHANGED
data/test/class_method_test.rb
CHANGED
@@ -12,7 +12,7 @@ class ClassMethodTest < Test::Unit::TestCase
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_soft_dependencies
|
15
|
-
assert_equal
|
15
|
+
assert_equal 21, Parent.soft_dependencies.count
|
16
16
|
|
17
17
|
assert Parent.soft_dependencies.include? :soft_children
|
18
18
|
assert Parent.soft_dependencies.include? :children
|
@@ -37,7 +37,9 @@ class ClassMethodTest < Test::Unit::TestCase
|
|
37
37
|
assert Parent.soft_dependencies.include? :soft_parent_sports
|
38
38
|
assert Parent.soft_dependencies.include? :parent_sports
|
39
39
|
assert Parent.soft_dependencies.include? :soft_parent_nickname
|
40
|
-
assert Parent.soft_dependencies.include? :parent_nickname
|
40
|
+
assert Parent.soft_dependencies.include? :parent_nickname
|
41
|
+
|
42
|
+
assert Parent.soft_dependencies.include? :soft_no_revive_with_parent_attribute_children
|
41
43
|
end
|
42
44
|
|
43
45
|
def test_soft_dependency_order
|
@@ -52,4 +52,26 @@ class DependentDeleteAllTest < Test::Unit::TestCase
|
|
52
52
|
assert_equal 0, SoftDeleteAllChild.where(:name => "bambam", :parent_id => @fred.id).count
|
53
53
|
end
|
54
54
|
|
55
|
+
# revive
|
56
|
+
|
57
|
+
def test_revive_does_not_delete_all_has_many_delete_all_soft_children
|
58
|
+
@fred.destroy
|
59
|
+
assert_equal true, @fred.deleted?
|
60
|
+
@fred.soft_delete_all_children << dino = SoftDeleteAllChild.new(:name => "dino")
|
61
|
+
assert_equal 1, @fred.reload.soft_delete_all_children.count
|
62
|
+
@fred.revive
|
63
|
+
assert_equal false, @fred.deleted?
|
64
|
+
assert_equal 1, SoftDeleteAllChild.where(:name => "dino", :parent_id => @fred.id).count
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_revive_does_not_delete_all_has_many_delete_all_children
|
68
|
+
@fred.destroy
|
69
|
+
assert_equal true, @fred.deleted?
|
70
|
+
@fred.delete_all_children << dino = DeleteAllChild.new(:name => "dino")
|
71
|
+
assert_equal 1, @fred.reload.delete_all_children.count
|
72
|
+
@fred.revive
|
73
|
+
assert_equal false, @fred.deleted?
|
74
|
+
assert_equal 1, DeleteAllChild.where(:name => "dino", :parent_id => @fred.id).count
|
75
|
+
end
|
76
|
+
|
55
77
|
end
|
@@ -46,4 +46,22 @@ class DependentDeleteTest < Test::Unit::TestCase
|
|
46
46
|
assert_equal 0, DeleteOne.where(:name => "pebbles", :parent_id => @fred.id).count
|
47
47
|
end
|
48
48
|
|
49
|
+
# revive
|
50
|
+
|
51
|
+
def test_revive_does_not_delete_all_has_one_soft_delete_one
|
52
|
+
@fred.destroy
|
53
|
+
@fred.soft_delete_one = bambam = SoftDeleteOne.new(:name => "bambam")
|
54
|
+
assert_equal bambam, @fred.reload.soft_delete_one
|
55
|
+
@fred.revive
|
56
|
+
assert_equal bambam, @fred.reload.soft_delete_one
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_revive_does_not_delete_all_has_one_delete_one
|
60
|
+
@fred.destroy
|
61
|
+
@fred.delete_one = bambam = DeleteOne.new(:name => "bambam")
|
62
|
+
assert_equal bambam, @fred.reload.delete_one
|
63
|
+
@fred.revive
|
64
|
+
assert_equal bambam, @fred.reload.delete_one
|
65
|
+
end
|
66
|
+
|
49
67
|
end
|
@@ -92,4 +92,33 @@ class DependentDestroyTest < Test::Unit::TestCase
|
|
92
92
|
assert_nil One.find_by_name("bambam")
|
93
93
|
end
|
94
94
|
|
95
|
+
# revive
|
96
|
+
|
97
|
+
def test_revive_has_many_soft_children
|
98
|
+
@fred.soft_children << pebbles = SoftChild.new(:name => "pebbles")
|
99
|
+
@fred.soft_children << bambam = SoftChild.new(:name => "bambam")
|
100
|
+
assert_equal @fred.reload.soft_children.count, 2
|
101
|
+
@fred.destroy
|
102
|
+
assert_equal @fred.deleted?, true
|
103
|
+
assert_equal pebbles.reload.deleted?, true
|
104
|
+
assert_equal bambam.reload.deleted?, true
|
105
|
+
@fred.soft_children << dino = SoftChild.new(:name => "dino")
|
106
|
+
@fred.revive
|
107
|
+
assert_equal @fred.deleted?, false
|
108
|
+
assert_equal pebbles.reload.deleted?, false
|
109
|
+
assert_equal bambam.reload.deleted?, false
|
110
|
+
assert_equal dino.reload.deleted?, false
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_revive_has_soft_ones
|
114
|
+
@fred.soft_one = bambam = SoftOne.new(:name => "bambam")
|
115
|
+
assert_equal @fred.reload.soft_one, SoftOne.where(:name => "bambam", :parent_id => @fred.id).first
|
116
|
+
@fred.destroy
|
117
|
+
assert_equal @fred.deleted?, true
|
118
|
+
assert_equal SoftOne.where(:name => "bambam").first.deleted?, true
|
119
|
+
@fred.revive
|
120
|
+
assert_equal @fred.deleted?, false
|
121
|
+
assert_equal SoftOne.where(:name => "bambam").first.deleted?, false
|
122
|
+
end
|
123
|
+
|
95
124
|
end
|
@@ -93,4 +93,39 @@ class DependentNullifyTest < Test::Unit::TestCase
|
|
93
93
|
assert_nil bambam.reload.parent
|
94
94
|
end
|
95
95
|
|
96
|
+
# revive
|
97
|
+
|
98
|
+
def test_revive_does_not_nullify_has_many_nullify_soft_children
|
99
|
+
@fred.destroy
|
100
|
+
@fred.soft_nullify_children << pebbles = SoftNullifyChild.new(:name => "pebbles")
|
101
|
+
assert_equal @fred, pebbles.reload.parent
|
102
|
+
@fred.revive
|
103
|
+
assert_equal @fred, pebbles.reload.parent
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_revive_does_not_nullify_has_many_nullify_children
|
107
|
+
@fred.destroy
|
108
|
+
@fred.nullify_children << pebbles = NullifyChild.new(:name => "pebbles")
|
109
|
+
assert_equal @fred, pebbles.reload.parent
|
110
|
+
@fred.revive
|
111
|
+
assert_equal @fred, pebbles.reload.parent
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_revive_does_not_nullify_has_soft_nullify_ones
|
115
|
+
@fred.destroy
|
116
|
+
@fred.soft_nullify_one = bambam = SoftNullifyOne.new(:name => "bambam")
|
117
|
+
assert_equal bambam, @fred.reload.soft_nullify_one
|
118
|
+
@fred.revive
|
119
|
+
assert_equal bambam, @fred.reload.soft_nullify_one
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_revive_does_not_nullify_has_nullify_ones
|
123
|
+
@fred.destroy
|
124
|
+
@fred.nullify_one = bambam = NullifyOne.new(:name => "bambam")
|
125
|
+
assert_equal bambam, @fred.reload.nullify_one
|
126
|
+
@fred.revive
|
127
|
+
assert_equal bambam, @fred.reload.nullify_one
|
128
|
+
end
|
129
|
+
|
130
|
+
|
96
131
|
end
|
@@ -152,5 +152,50 @@ class DependentRestrictTest < Test::Unit::TestCase
|
|
152
152
|
assert_equal false, @fred.deleted?
|
153
153
|
assert_not_nil RestrictOne.find_by_name("bambam")
|
154
154
|
end
|
155
|
-
|
155
|
+
|
156
|
+
# revive
|
157
|
+
|
158
|
+
def test_revive_has_many_restrict_children_not_empty
|
159
|
+
assert_can_revive do
|
160
|
+
@fred.restrict_children << pebbles = RestrictChild.new(:name => "pebbles")
|
161
|
+
assert_equal pebbles, @fred.restrict_children.first
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_revive_has_many_restrict_soft_children_not_all_deleted
|
166
|
+
assert_can_revive do
|
167
|
+
@fred.soft_restrict_children << pebbles = SoftRestrictChild.new(:name => "pebbles")
|
168
|
+
assert_equal pebbles, @fred.soft_restrict_children.first
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_revive_has_restrict_ones_not_nil
|
173
|
+
assert_can_revive do
|
174
|
+
@fred.restrict_one = bambam = RestrictOne.new(:name => "bambam")
|
175
|
+
assert_equal bambam, @fred.restrict_one
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_revive_has_soft_restrict_ones_not_deleted
|
180
|
+
assert_can_revive do
|
181
|
+
@fred.soft_restrict_one = bambam = SoftRestrictOne.new(:name => "bambam")
|
182
|
+
assert_equal bambam, @fred.soft_restrict_one
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def assert_can_revive
|
189
|
+
assert_equal true, @fred.destroy
|
190
|
+
yield if block_given?
|
191
|
+
# assert_not_raise ActiveRecord::DeleteRestrictionError do
|
192
|
+
@fred.revive
|
193
|
+
# end
|
194
|
+
assert_equal false, @fred.deleted?
|
195
|
+
end
|
196
|
+
|
197
|
+
def assert_true value
|
198
|
+
assert_equal true, value
|
199
|
+
end
|
200
|
+
|
156
201
|
end
|
data/test/test_helper.rb
CHANGED
@@ -190,6 +190,14 @@ def setup_db
|
|
190
190
|
t.references :callback_parent
|
191
191
|
end
|
192
192
|
|
193
|
+
# used to test behavior of soft_destroy migration without revive_with_parent column
|
194
|
+
create_table :soft_no_revive_with_parent_attribute_children do |t|
|
195
|
+
t.string :name
|
196
|
+
t.references :parent
|
197
|
+
t.boolean :deleted, :default => false
|
198
|
+
t.datetime :deleted_at
|
199
|
+
end
|
200
|
+
|
193
201
|
# todo: HasAndBelongsToMany?
|
194
202
|
end
|
195
203
|
end
|
@@ -236,6 +244,8 @@ class Parent < ActiveRecord::Base
|
|
236
244
|
has_one :soft_delete_one, :dependent => :delete
|
237
245
|
has_one :delete_one, :dependent => :delete
|
238
246
|
|
247
|
+
has_many :soft_no_revive_with_parent_attribute_children, :dependent => :destroy
|
248
|
+
|
239
249
|
soft_destroyable
|
240
250
|
end
|
241
251
|
|
@@ -393,3 +403,8 @@ class CallbackChild < ActiveRecord::Base
|
|
393
403
|
raise PreventDestroyBangError.new
|
394
404
|
end
|
395
405
|
end
|
406
|
+
|
407
|
+
class SoftNoReviveWithParentAttributeChild < ActiveRecord::Base
|
408
|
+
belongs_to :parent
|
409
|
+
soft_destroyable
|
410
|
+
end
|