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 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