soft_destroyable 0.1.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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. Copyright
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.8
1
+ 0.5.0
@@ -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
- update_attributes(:deleted_at => nil, :deleted => false)
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.destroy
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
- def cascade_to_soft_dependents(&block)
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 = reflection_for(assoc_sym)
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 = reflection_for(assoc_sym)
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
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{soft_destroyable}
8
- s.version = "0.1.8"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Kintzer"]
@@ -12,7 +12,7 @@ class ClassMethodTest < Test::Unit::TestCase
12
12
  end
13
13
 
14
14
  def test_soft_dependencies
15
- assert_equal 20, Parent.soft_dependencies.count
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
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 8
10
- version: 0.1.8
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Michael Kintzer