soft_deletion 0.1.10 → 0.2.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/.gitignore ADDED
@@ -0,0 +1 @@
1
+ gemfiles/*.gemfile.lock
data/Gemfile CHANGED
@@ -1,11 +1,10 @@
1
1
  source :rubygems
2
2
  gemspec
3
3
 
4
- gem 'appraisal'
5
4
  gem 'activerecord'
6
5
  gem 'activesupport'
6
+ gem 'appraisal'
7
7
  gem 'rake'
8
- gem 'shoulda'
9
- gem 'mocha'
10
8
  gem 'sqlite3'
11
- gem 'test-unit', '2.2' # >2.2 does not show dots
9
+ gem 'rspec'
10
+ gem 'database_cleaner'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- soft_deletion (0.1.10)
4
+ soft_deletion (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -22,20 +22,20 @@ GEM
22
22
  rake
23
23
  arel (3.0.2)
24
24
  builder (3.0.0)
25
+ database_cleaner (0.8.0)
26
+ diff-lcs (1.1.3)
25
27
  i18n (0.6.0)
26
- metaclass (0.0.1)
27
- mocha (0.12.3)
28
- metaclass (~> 0.0.1)
29
28
  multi_json (1.3.6)
30
29
  rake (0.9.2.2)
31
- shoulda (3.1.1)
32
- shoulda-context (~> 1.0)
33
- shoulda-matchers (~> 1.2)
34
- shoulda-context (1.0.0)
35
- shoulda-matchers (1.2.0)
36
- activesupport (>= 3.0.0)
30
+ rspec (2.11.0)
31
+ rspec-core (~> 2.11.0)
32
+ rspec-expectations (~> 2.11.0)
33
+ rspec-mocks (~> 2.11.0)
34
+ rspec-core (2.11.1)
35
+ rspec-expectations (2.11.3)
36
+ diff-lcs (~> 1.1.3)
37
+ rspec-mocks (2.11.3)
37
38
  sqlite3 (1.3.6)
38
- test-unit (2.2.0)
39
39
  tzinfo (0.3.33)
40
40
 
41
41
  PLATFORMS
@@ -45,9 +45,8 @@ DEPENDENCIES
45
45
  activerecord
46
46
  activesupport
47
47
  appraisal
48
- mocha
48
+ database_cleaner
49
49
  rake
50
- shoulda
50
+ rspec
51
51
  soft_deletion!
52
52
  sqlite3
53
- test-unit (= 2.2)
data/Rakefile CHANGED
@@ -1,13 +1,12 @@
1
1
  require 'appraisal'
2
2
  require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
3
4
 
4
5
  task :default do
5
- sh "bundle exec rake appraisal:install && bundle exec rake appraisal test"
6
+ sh "bundle exec rake appraisal:install && bundle exec rake appraisal spec"
6
7
  end
7
8
 
8
- task :test do
9
- sh "ruby -Itest test/soft_deletion_test.rb"
10
- end
9
+ RSpec::Core::RakeTask.new(:spec)
11
10
 
12
11
  # extracted from https://github.com/grosser/project_template
13
12
  rule /^version:bump:.*/ do |t|
data/Readme.md CHANGED
@@ -1,19 +1,16 @@
1
- Explicit soft deletion for ActiveRecord via deleted_at and default scope + callbacks.<br/>
1
+ Explicit soft deletion for ActiveRecord via deleted_at + callbacks and optional default scope.<br/>
2
2
  Not overwriting destroy or delete.
3
3
 
4
4
  Install
5
5
  =======
6
6
  gem install soft_deletion
7
- Or
8
-
9
- rails plugin install git://github.com/grosser/soft_deletion.git
10
-
11
7
 
12
8
  Usage
13
9
  =====
14
- # mix into any model ...
10
+ require 'soft_deletion'
11
+
15
12
  class User < ActiveRecord::Base
16
- include SoftDeletion
13
+ has_soft_deletion :default_scope => true
17
14
 
18
15
  after_soft_delete :send_deletion_emails # Rails 2 + 3
19
16
  set_callback :soft_delete, :after, :prepare_emails # Rails 3
@@ -44,7 +41,7 @@ Usage
44
41
 
45
42
  TODO
46
43
  ====
47
- - Rails 3 from with inspiration from https://github.com/JackDanger/permanent_records/blob/master/lib/permanent_records.rb
44
+ - Rails 3 with inspiration from https://github.com/JackDanger/permanent_records/blob/master/lib/permanent_records.rb
48
45
  - maybe stuff from https://github.com/smoku/soft_delete
49
46
 
50
47
  Authors
@@ -4,10 +4,9 @@ source :rubygems
4
4
 
5
5
  gem "appraisal"
6
6
  gem "rake"
7
- gem "shoulda"
8
- gem "mocha"
9
7
  gem "sqlite3"
10
- gem "test-unit", "2.2"
8
+ gem "rspec"
9
+ gem "database_cleaner"
11
10
  gem "activerecord", "2.3.14"
12
11
  gem "activesupport", "2.3.14"
13
12
 
@@ -4,10 +4,9 @@ source :rubygems
4
4
 
5
5
  gem "appraisal"
6
6
  gem "rake"
7
- gem "shoulda"
8
- gem "mocha"
9
7
  gem "sqlite3"
10
- gem "test-unit", "2.2"
8
+ gem "rspec"
9
+ gem "database_cleaner"
11
10
  gem "activerecord", "3.2.3"
12
11
  gem "activesupport", "3.2.3"
13
12
 
data/lib/soft_deletion.rb CHANGED
@@ -1,129 +1,7 @@
1
1
  require 'active_record'
2
2
  require 'soft_deletion/version'
3
+ require 'soft_deletion/core'
3
4
  require 'soft_deletion/dependency'
5
+ require 'soft_deletion/setup'
4
6
 
5
- module SoftDeletion
6
- def self.included(base)
7
- unless base.ancestors.include?(ActiveRecord::Base)
8
- raise "You can only include this if #{base} extends ActiveRecord::Base"
9
- end
10
- base.extend(ClassMethods)
11
-
12
- if base.respond_to?(:define_default_soft_delete_scope)
13
- base.define_default_soft_delete_scope
14
- elsif base.table_exists? && base.column_names.include?('deleted_at')
15
- # Avoids a bad SQL request with versions of code without the column deleted_at (for example a migration prior to the migration
16
- # that adds deleted_at)
17
- base.send(:default_scope, :conditions => { :deleted_at => nil })
18
- end
19
-
20
- # backport after_soft_delete so we can safely upgrade to rails 3
21
- if ActiveRecord::VERSION::MAJOR > 2
22
- base.define_callbacks :soft_delete
23
- class << base
24
- def before_soft_delete(*callbacks)
25
- set_callback :soft_delete, :before, *callbacks
26
- end
27
-
28
- def after_soft_delete(*callbacks)
29
- set_callback :soft_delete, :after, *callbacks
30
- end
31
- end
32
- else
33
- base.define_callbacks :before_soft_delete
34
- base.define_callbacks :after_soft_delete
35
- end
36
- end
37
-
38
- module ClassMethods
39
- def soft_delete_dependents
40
- self.reflect_on_all_associations.
41
- select { |a| [:destroy, :delete_all, :nullify].include?(a.options[:dependent]) }.
42
- map(&:name)
43
- end
44
-
45
- def with_deleted
46
- with_exclusive_scope do
47
- yield self
48
- end
49
- end
50
-
51
- def mark_as_soft_deleted_sql
52
- ["deleted_at = ?", Time.now]
53
- end
54
-
55
- def soft_delete_all!(ids_or_models)
56
- ids_or_models = Array.wrap(ids_or_models)
57
-
58
- if ids_or_models.first.is_a?(ActiveRecord::Base)
59
- ids = ids_or_models.map(&:id)
60
- models = ids_or_models
61
- else
62
- ids = ids_or_models
63
- models = all(:conditions => { :id => ids })
64
- end
65
-
66
- transaction do
67
- update_all(mark_as_soft_deleted_sql, :id => ids)
68
- models.each do |model|
69
- model.soft_delete_dependencies.each(&:soft_delete!)
70
- model.run_callbacks ActiveRecord::VERSION::MAJOR > 2 ? :soft_delete : :after_soft_delete
71
- end
72
- end
73
- end
74
- end
75
-
76
- def deleted?
77
- deleted_at.present?
78
- end
79
-
80
- def mark_as_deleted
81
- self.deleted_at = Time.now
82
- end
83
-
84
- def mark_as_undeleted
85
- self.deleted_at = nil
86
- end
87
-
88
- def soft_delete!
89
- _run_soft_delete { save! }
90
- end
91
-
92
- def soft_delete(*args)
93
- _run_soft_delete{ save(*args) }
94
- end
95
-
96
- def soft_undelete!
97
- self.class.transaction do
98
- mark_as_undeleted
99
- soft_delete_dependencies.each(&:soft_undelete!)
100
- save!
101
- end
102
- end
103
-
104
- def soft_delete_dependencies
105
- self.class.soft_delete_dependents.map { |dependent| Dependency.new(self, dependent) }
106
- end
107
-
108
- protected
109
-
110
- def _run_soft_delete(&block)
111
- self.class.transaction do
112
- result = nil
113
- if ActiveRecord::VERSION::MAJOR > 2
114
- run_callbacks :soft_delete do
115
- mark_as_deleted
116
- soft_delete_dependencies.each(&:soft_delete!)
117
- result = block.call
118
- end
119
- else
120
- run_callbacks :before_soft_delete
121
- mark_as_deleted
122
- soft_delete_dependencies.each(&:soft_delete!)
123
- result = block.call
124
- run_callbacks :after_soft_delete
125
- end
126
- result
127
- end
128
- end
129
- end
7
+ ActiveRecord::Base.send(:include, SoftDeletion::Setup)
@@ -0,0 +1,119 @@
1
+ module SoftDeletion
2
+ module Core
3
+ def self.included(base)
4
+ unless base.ancestors.include?(ActiveRecord::Base)
5
+ raise "You can only include this if #{base} extends ActiveRecord::Base"
6
+ end
7
+ base.extend(ClassMethods)
8
+
9
+ # backport after_soft_delete so we can safely upgrade to rails 3
10
+ if ActiveRecord::VERSION::MAJOR > 2
11
+ base.define_callbacks :soft_delete
12
+ class << base
13
+ def before_soft_delete(*callbacks)
14
+ set_callback :soft_delete, :before, *callbacks
15
+ end
16
+
17
+ def after_soft_delete(*callbacks)
18
+ set_callback :soft_delete, :after, *callbacks
19
+ end
20
+ end
21
+ else
22
+ base.define_callbacks :before_soft_delete
23
+ base.define_callbacks :after_soft_delete
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def soft_delete_dependents
29
+ self.reflect_on_all_associations.
30
+ select { |a| [:destroy, :delete_all, :nullify].include?(a.options[:dependent]) }.
31
+ map(&:name)
32
+ end
33
+
34
+ def with_deleted
35
+ with_exclusive_scope do
36
+ yield self
37
+ end
38
+ end
39
+
40
+ def mark_as_soft_deleted_sql
41
+ ["deleted_at = ?", Time.now]
42
+ end
43
+
44
+ def soft_delete_all!(ids_or_models)
45
+ ids_or_models = Array.wrap(ids_or_models)
46
+
47
+ if ids_or_models.first.is_a?(ActiveRecord::Base)
48
+ ids = ids_or_models.map(&:id)
49
+ models = ids_or_models
50
+ else
51
+ ids = ids_or_models
52
+ models = all(:conditions => { :id => ids })
53
+ end
54
+
55
+ transaction do
56
+ update_all(mark_as_soft_deleted_sql, :id => ids)
57
+ models.each do |model|
58
+ model.soft_delete_dependencies.each(&:soft_delete!)
59
+ model.run_callbacks ActiveRecord::VERSION::MAJOR > 2 ? :soft_delete : :after_soft_delete
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def deleted?
66
+ deleted_at.present?
67
+ end
68
+
69
+ def mark_as_deleted
70
+ self.deleted_at = Time.now
71
+ end
72
+
73
+ def mark_as_undeleted
74
+ self.deleted_at = nil
75
+ end
76
+
77
+ def soft_delete!
78
+ _run_soft_delete { save! }
79
+ end
80
+
81
+ def soft_delete(*args)
82
+ _run_soft_delete{ save(*args) }
83
+ end
84
+
85
+ def soft_undelete!
86
+ self.class.transaction do
87
+ mark_as_undeleted
88
+ soft_delete_dependencies.each(&:soft_undelete!)
89
+ save!
90
+ end
91
+ end
92
+
93
+ def soft_delete_dependencies
94
+ self.class.soft_delete_dependents.map { |dependent| SoftDeletion::Dependency.new(self, dependent) }
95
+ end
96
+
97
+ protected
98
+
99
+ def _run_soft_delete(&block)
100
+ self.class.transaction do
101
+ result = nil
102
+ if ActiveRecord::VERSION::MAJOR > 2
103
+ run_callbacks :soft_delete do
104
+ mark_as_deleted
105
+ soft_delete_dependencies.each(&:soft_delete!)
106
+ result = block.call
107
+ end
108
+ else
109
+ run_callbacks :before_soft_delete
110
+ mark_as_deleted
111
+ soft_delete_dependencies.each(&:soft_delete!)
112
+ result = block.call
113
+ run_callbacks :after_soft_delete
114
+ end
115
+ result
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,32 @@
1
+ module SoftDeletion
2
+ module Setup
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # When you call this, it will include the core module and its methods
10
+ #
11
+ # Options:
12
+ #
13
+ # *default_scope*, value: true/false
14
+ # If true, it will also define a default scope
15
+ #
16
+ # It will check if the column "deleted_at" exist before applying default scope
17
+ def has_soft_deletion(options={})
18
+ default_options = {:default_scope => false}
19
+
20
+ include SoftDeletion::Core
21
+
22
+ options = default_options.merge(options)
23
+
24
+ if options[:default_scope] && table_exists? && column_names.include?("deleted_at")
25
+ # Avoids a bad SQL request with versions of code without the column deleted_at (for example a migration prior to the migration
26
+ # that adds deleted_at)
27
+ default_scope :conditions => { :deleted_at => nil }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module SoftDeletion
2
- VERSION = '0.1.10'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,321 @@
1
+ require 'spec_helper'
2
+
3
+ describe SoftDeletion do
4
+ def self.successfully_soft_deletes
5
+ context "successfully soft deleted" do
6
+ before do
7
+ @category.soft_delete!
8
+ end
9
+
10
+ it "should mark itself as deleted" do
11
+ @category.reload
12
+ @category.should be_deleted
13
+ end
14
+
15
+ it "should soft delete its dependent associations" do
16
+ @forum.reload
17
+ @forum.should be_deleted
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.successfully_bulk_soft_deletes
23
+ context "successfully bulk soft deleted" do
24
+ before do
25
+ Category.soft_delete_all!(@category)
26
+ end
27
+
28
+ it "should mark itself as deleted" do
29
+ @category.reload
30
+ @category.should be_deleted
31
+ end
32
+
33
+ it "should soft delete its dependent associations" do
34
+ @forum.reload
35
+ @forum.should be_deleted
36
+ end
37
+ end
38
+ end
39
+
40
+ before do
41
+ clear_callbacks Category, :soft_delete
42
+
43
+ # Stub dump method calls
44
+ Category.any_instance.stub(:foo)
45
+ Category.any_instance.stub(:bar)
46
+ end
47
+
48
+ describe "callbacks" do
49
+ describe ".before_soft_delete" do
50
+ it "should be called on soft-deletion" do
51
+ Category.before_soft_delete :foo
52
+ category = Category.create!
53
+
54
+ category.should_receive(:foo)
55
+
56
+ category.soft_delete!
57
+ end
58
+ end
59
+
60
+ describe ".after_soft_delete" do
61
+ it "should be called after soft-deletion" do
62
+ Category.after_soft_delete :foo
63
+ category = Category.create!
64
+
65
+ category.should_receive(:foo)
66
+
67
+ category.soft_delete!
68
+ end
69
+
70
+ it "should be called after bulk soft-deletion" do
71
+ Category.after_soft_delete :foo
72
+ category = Category.create!
73
+
74
+ category.should_receive(:foo)
75
+
76
+ Category.soft_delete_all!(category)
77
+ end
78
+
79
+ it "should be call multiple after soft-deletion" do
80
+ Category.after_soft_delete :foo, :bar
81
+ category = Category.create!
82
+
83
+ category.should_receive(:foo)
84
+ category.should_receive(:bar)
85
+
86
+ category.soft_delete!
87
+ end
88
+
89
+ it "should not be called after normal destroy" do
90
+ Category.after_soft_delete :foo
91
+ category = Category.create!
92
+
93
+ category.should_not_receive(:foo)
94
+
95
+ category.destroy
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "association" do
101
+ context "without dependent associations" do
102
+ it "should only soft-delete itself" do
103
+ category = NACategory.create!
104
+ category.soft_delete!
105
+
106
+ category.reload
107
+ category.should be_deleted
108
+ end
109
+ end
110
+
111
+ context "with independent associations" do
112
+ it "should not delete associations" do
113
+ category = IDACategory.create!
114
+ forum = category.forums.create!
115
+ category.soft_delete!
116
+
117
+ forum.reload
118
+ forum.should be_deleted
119
+ end
120
+ end
121
+
122
+ context "with dependent has_one association" do
123
+ before do
124
+ @category = HOACategory.create!
125
+ @forum = @category.create_forum
126
+ end
127
+
128
+ successfully_soft_deletes
129
+ successfully_bulk_soft_deletes
130
+ end
131
+
132
+ context "with dependent association that doesn't have soft deletion" do
133
+ before do
134
+ @category = DACategory.create!
135
+ @forum = @category.destroyable_forums.create!
136
+ end
137
+
138
+ context "successfully soft deleted" do
139
+ before do
140
+ @category.soft_delete!
141
+ end
142
+
143
+ it "should mark itself as deleted" do
144
+ @category.reload
145
+ @category.should be_deleted
146
+ end
147
+
148
+ it "should not destroy dependent association" do
149
+ DestroyableForum.exists?(@forum.id).should be_true
150
+ end
151
+ end
152
+ end
153
+
154
+ context "with dependent has_many associations" do
155
+ before do
156
+ @category = Category.create!
157
+ @forum = @category.forums.create!
158
+ end
159
+
160
+ context "failing to soft delete" do
161
+ before do
162
+ @category.stub(:valid?).and_return(false)
163
+ expect{ @category.soft_delete! }.to raise_error(ActiveRecord::RecordInvalid)
164
+ end
165
+
166
+ it "should not mark itself as deleted" do
167
+ @category.reload
168
+ @category.should_not be_deleted
169
+ end
170
+
171
+ it "should not soft delete its dependent associations" do
172
+ @forum.reload
173
+ @forum.should_not be_deleted
174
+ end
175
+ end
176
+
177
+ successfully_soft_deletes
178
+ successfully_bulk_soft_deletes
179
+
180
+ context "being restored from soft deletion" do
181
+ before do
182
+ @category.soft_delete!
183
+ Category.with_deleted do
184
+ @category.reload
185
+ @category.soft_undelete!
186
+ end
187
+ end
188
+
189
+ it "should not mark itself as deleted" do
190
+ @category.reload
191
+ @category.should_not be_deleted
192
+ end
193
+
194
+ it "should restore its dependent associations" do
195
+ @forum.reload
196
+ @forum.should_not be_deleted
197
+ end
198
+ end
199
+ end
200
+
201
+ context "a soft-deleted has-many category that nullifies forum references on delete" do
202
+ it "should nullify those references" do
203
+ category = NDACategory.create!
204
+ forum = category.forums.create!
205
+ category.soft_delete!
206
+
207
+ forum.reload
208
+ forum.should be_deleted
209
+ #forum.category_id.should be_nil # TODO
210
+ end
211
+ end
212
+ end
213
+
214
+ context "without deleted_at column" do
215
+ it "should default scope should not provoke an error" do
216
+ expect do
217
+ OriginalCategory.create!
218
+ end.to_not raise_error
219
+ end
220
+ end
221
+
222
+ describe ".soft_delete_all!" do
223
+ before do
224
+ @categories = 2.times.map { Category.create! }
225
+ end
226
+
227
+ context "by id" do
228
+ before do
229
+ Category.soft_delete_all!(@categories.map(&:id))
230
+ end
231
+
232
+ it "should delete all models" do
233
+ @categories.each do |category|
234
+ category.reload
235
+ category.should be_deleted
236
+ end
237
+ end
238
+ end
239
+
240
+ context "by model" do
241
+ before do
242
+ Category.soft_delete_all!(@categories)
243
+ end
244
+
245
+ it "should delete all models" do
246
+ @categories.each do |category|
247
+ category.reload
248
+ category.should be_deleted
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ describe "overwritten default scope" do
255
+ it "should find even with deleted_at" do
256
+ forum = Cat1Forum.create(:deleted_at => Time.now)
257
+
258
+ Cat1Forum.find_by_id(forum.id).should_not be_nil
259
+ end
260
+
261
+ it "should not find by new scope" do
262
+ # create! does not work here on rails 2
263
+ forum = Cat1Forum.new
264
+ forum.category_id = 2
265
+ forum.save!
266
+
267
+ Cat1Forum.find_by_id(forum.id).should be_nil
268
+ end
269
+ end
270
+
271
+ describe "validations" do
272
+ it "should fail when validations fail" do
273
+ forum = ValidatedForum.create!(:category_id => 1)
274
+ forum.category_id = nil
275
+
276
+ expect do
277
+ forum.soft_delete!
278
+ end.to raise_error(ActiveRecord::RecordInvalid)
279
+
280
+ forum.reload
281
+ forum.should_not be_deleted
282
+ end
283
+
284
+ it "should pass when validations pass" do
285
+ forum = ValidatedForum.create!(:category_id => 1)
286
+ forum.soft_delete!
287
+
288
+ forum.reload
289
+ forum.should be_deleted
290
+ end
291
+ end
292
+
293
+ describe "#soft_delete" do
294
+ it "should return true if it succeeds" do
295
+ forum = ValidatedForum.create!(:category_id => 1)
296
+
297
+ forum.soft_delete.should be_true
298
+ forum.reload
299
+ forum.should be_deleted
300
+ end
301
+
302
+ it "should return false if validations fail" do
303
+ forum = ValidatedForum.create!(:category_id => 1)
304
+ forum.category_id = nil
305
+
306
+ forum.soft_delete.should be_false
307
+ forum.reload
308
+ forum.should_not be_deleted
309
+ end
310
+
311
+ it "should return true if validations are prevented and it succeeds" do
312
+ forum = ValidatedForum.create!(:category_id => 1)
313
+ forum.category_id = nil
314
+ skip_validations = (ActiveRecord::VERSION::MAJOR == 2 ? false : {:validate => false})
315
+
316
+ forum.soft_delete(skip_validations).should be_true
317
+ forum.reload
318
+ forum.should be_deleted
319
+ end
320
+ end
321
+ end
@@ -1,8 +1,25 @@
1
- require 'active_support/test_case'
2
- require 'shoulda'
3
1
  require 'active_record'
2
+ require 'soft_deletion'
3
+ require 'database_cleaner'
4
+ require 'logger'
5
+
6
+ # ActiveRecord::Base.logger = Logger.new(STDOUT) # for easier debugging
7
+
8
+ RSpec.configure do |config|
9
+ config.before(:suite) do
10
+ # would be nicer to use transaction, but Rails 2 does not open a new transaction or savepoint
11
+ # when already inside a transaction
12
+ DatabaseCleaner.strategy = :truncation
13
+ end
14
+
15
+ config.before do
16
+ DatabaseCleaner.start
17
+ end
4
18
 
5
- ENV['EMACS'] = 't' # colors for test-unit < 2.4.9
19
+ config.after do
20
+ DatabaseCleaner.clean
21
+ end
22
+ end
6
23
 
7
24
  def clear_callbacks(model, callback)
8
25
  if ActiveRecord::VERSION::MAJOR > 2
@@ -47,61 +64,73 @@ class ActiveRecord::Base
47
64
  end
48
65
 
49
66
  # setup models
50
- require 'soft_deletion'
51
67
 
52
68
  class Forum < ActiveRecord::Base
53
- include SoftDeletion
69
+ has_soft_deletion
70
+
54
71
  belongs_to :category
55
72
  end
56
73
 
57
74
  class ValidatedForum < ActiveRecord::Base
58
75
  silent_set_table_name 'forums'
59
- include SoftDeletion
76
+
77
+ has_soft_deletion
78
+
60
79
  belongs_to :category
61
80
  validates_presence_of :category_id
62
81
  end
63
82
 
64
83
  class Category < ActiveRecord::Base
65
- include SoftDeletion
84
+ has_soft_deletion
85
+
66
86
  has_many :forums, :dependent => :destroy
67
87
  end
68
88
 
69
89
  # No association
70
90
  class NACategory < ActiveRecord::Base
71
91
  silent_set_table_name 'categories'
72
- include SoftDeletion
92
+
93
+ has_soft_deletion
73
94
  end
74
95
 
75
96
  # Independent association
76
97
  class IDACategory < ActiveRecord::Base
77
98
  silent_set_table_name 'categories'
78
- include SoftDeletion
99
+
100
+ has_soft_deletion
101
+
79
102
  has_many :forums, :dependent => :destroy, :foreign_key => :category_id
80
103
  end
81
104
 
82
105
  # Nullified dependent association
83
106
  class NDACategory < ActiveRecord::Base
84
107
  silent_set_table_name 'categories'
85
- include SoftDeletion
108
+
109
+ has_soft_deletion
110
+
86
111
  has_many :forums, :dependent => :destroy, :foreign_key => :category_id
87
112
  end
88
113
 
89
114
  # Has ome association
90
115
  class HOACategory < ActiveRecord::Base
91
116
  silent_set_table_name 'categories'
92
- include SoftDeletion
117
+
118
+ has_soft_deletion
119
+
93
120
  has_one :forum, :dependent => :destroy, :foreign_key => :category_id
94
121
  end
95
122
 
96
123
  # Class without column deleted_at
97
124
  class OriginalCategory < ActiveRecord::Base
98
- include SoftDeletion
125
+ has_soft_deletion
99
126
  end
100
127
 
101
128
  # Has many destroyable association
102
129
  class DACategory < ActiveRecord::Base
103
130
  silent_set_table_name 'categories'
104
- include SoftDeletion
131
+
132
+ has_soft_deletion
133
+
105
134
  has_many :destroyable_forums, :dependent => :destroy, :foreign_key => :category_id
106
135
  end
107
136
 
@@ -112,18 +141,17 @@ end
112
141
 
113
142
  # test that it does not blow up when the table is not yet defined (e.g. in rake db:reset)
114
143
  class NoTable < ActiveRecord::Base
115
- include SoftDeletion
144
+ has_soft_deletion
116
145
  end
117
146
 
118
147
  # Forum with other default scope
119
148
  class Cat1Forum < ActiveRecord::Base
120
149
  silent_set_table_name 'forums'
121
150
 
122
- def self.define_default_soft_delete_scope
123
- default_scope :conditions => {:category_id => 1}
124
- end
151
+ has_soft_deletion
152
+ default_scope :conditions => {:category_id => 1}
153
+
125
154
 
126
- include SoftDeletion
127
155
  belongs_to :category
128
156
  end
129
157
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soft_deletion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-07 00:00:00.000000000 Z
12
+ date: 2012-10-15 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: michael@grosser.it
@@ -17,6 +17,7 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - .gitignore
20
21
  - .travis.yml
21
22
  - Appraisals
22
23
  - Gemfile
@@ -25,15 +26,15 @@ files:
25
26
  - Rakefile
26
27
  - Readme.md
27
28
  - gemfiles/rails2.gemfile
28
- - gemfiles/rails2.gemfile.lock
29
29
  - gemfiles/rails3.gemfile
30
- - gemfiles/rails3.gemfile.lock
31
30
  - lib/soft_deletion.rb
31
+ - lib/soft_deletion/core.rb
32
32
  - lib/soft_deletion/dependency.rb
33
+ - lib/soft_deletion/setup.rb
33
34
  - lib/soft_deletion/version.rb
34
35
  - soft_deletion.gemspec
35
- - test/soft_deletion_test.rb
36
- - test/test_helper.rb
36
+ - spec/soft_deletion_spec.rb
37
+ - spec/spec_helper.rb
37
38
  homepage: http://github.com/grosser/soft_deletion
38
39
  licenses:
39
40
  - MIT
@@ -49,7 +50,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
50
  version: '0'
50
51
  segments:
51
52
  - 0
52
- hash: -1755831677353429354
53
+ hash: -3275884841840712801
53
54
  required_rubygems_version: !ruby/object:Gem::Requirement
54
55
  none: false
55
56
  requirements:
@@ -58,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
59
  version: '0'
59
60
  segments:
60
61
  - 0
61
- hash: -1755831677353429354
62
+ hash: -3275884841840712801
62
63
  requirements: []
63
64
  rubyforge_project:
64
65
  rubygems_version: 1.8.24
@@ -1,33 +0,0 @@
1
- PATH
2
- remote: /Users/mgrosser/code/tools/soft_deletion
3
- specs:
4
- soft_deletion (0.1.9)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- activerecord (2.3.14)
10
- activesupport (= 2.3.14)
11
- activesupport (2.3.14)
12
- appraisal (0.4.1)
13
- bundler
14
- rake
15
- mocha (0.9.12)
16
- rake (0.9.2.2)
17
- shoulda (2.10.3)
18
- sqlite3 (1.3.6)
19
- test-unit (2.2.0)
20
-
21
- PLATFORMS
22
- ruby
23
-
24
- DEPENDENCIES
25
- activerecord (= 2.3.14)
26
- activesupport (= 2.3.14)
27
- appraisal
28
- mocha
29
- rake
30
- shoulda
31
- soft_deletion!
32
- sqlite3
33
- test-unit (= 2.2)
@@ -1,46 +0,0 @@
1
- PATH
2
- remote: /Users/mgrosser/code/tools/soft_deletion
3
- specs:
4
- soft_deletion (0.1.9)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- activemodel (3.2.3)
10
- activesupport (= 3.2.3)
11
- builder (~> 3.0.0)
12
- activerecord (3.2.3)
13
- activemodel (= 3.2.3)
14
- activesupport (= 3.2.3)
15
- arel (~> 3.0.2)
16
- tzinfo (~> 0.3.29)
17
- activesupport (3.2.3)
18
- i18n (~> 0.6)
19
- multi_json (~> 1.0)
20
- appraisal (0.4.1)
21
- bundler
22
- rake
23
- arel (3.0.2)
24
- builder (3.0.0)
25
- i18n (0.6.0)
26
- mocha (0.9.12)
27
- multi_json (1.3.4)
28
- rake (0.9.2.2)
29
- shoulda (2.10.3)
30
- sqlite3 (1.3.6)
31
- test-unit (2.2.0)
32
- tzinfo (0.3.33)
33
-
34
- PLATFORMS
35
- ruby
36
-
37
- DEPENDENCIES
38
- activerecord (= 3.2.3)
39
- activesupport (= 3.2.3)
40
- appraisal
41
- mocha
42
- rake
43
- shoulda
44
- soft_deletion!
45
- sqlite3
46
- test-unit (= 2.2)
@@ -1,286 +0,0 @@
1
- require File.expand_path '../test_helper', __FILE__
2
-
3
- class SoftDeletionTest < ActiveSupport::TestCase
4
- def assert_deleted(resource)
5
- resource.class.with_deleted do
6
- resource.reload
7
- assert resource.deleted?
8
- end
9
- end
10
-
11
- def assert_not_deleted(resource)
12
- resource.reload
13
- assert !resource.deleted?
14
- end
15
-
16
- def self.successfully_soft_deletes
17
- context "successfully soft deleted" do
18
- setup do
19
- @category.soft_delete!
20
- end
21
-
22
- should "mark itself as deleted" do
23
- assert_deleted @category
24
- end
25
-
26
- should "soft delete its dependent associations" do
27
- assert_deleted @forum
28
- end
29
- end
30
- end
31
-
32
- def self.successfully_bulk_soft_deletes
33
- context "successfully bulk soft deleted" do
34
- setup do
35
- Category.soft_delete_all!(@category)
36
- end
37
-
38
- should "mark itself as deleted" do
39
- assert_deleted @category
40
- end
41
-
42
- should "soft delete its dependent associations" do
43
- assert_deleted @forum
44
- end
45
- end
46
- end
47
-
48
- setup do
49
- clear_callbacks Category, :soft_delete
50
- end
51
-
52
- context ".before_soft_delete" do
53
- should "be called on soft-deletion" do
54
- Category.before_soft_delete :foo
55
- category = Category.create!
56
- category.expects(:foo)
57
- category.soft_delete!
58
- end
59
- end
60
-
61
- context ".after_soft_delete" do
62
- should "be called after soft-deletion" do
63
- Category.after_soft_delete :foo
64
- category = Category.create!
65
- category.expects(:foo)
66
- category.soft_delete!
67
- end
68
-
69
- should "be called after bulk soft-deletion" do
70
- Category.after_soft_delete :foo
71
- category = Category.create!
72
- category.expects(:foo)
73
- Category.soft_delete_all!(category)
74
- end
75
-
76
- should "be call multiple after soft-deletion" do
77
- Category.after_soft_delete :foo, :bar
78
- category = Category.create!
79
- category.expects(:foo)
80
- category.expects(:bar)
81
- category.soft_delete!
82
- end
83
-
84
- should "not be called after normal destroy" do
85
- Category.after_soft_delete :foo
86
- category = Category.create!
87
- category.expects(:foo).never
88
- category.destroy
89
- end
90
- end
91
-
92
- context "without dependent associations" do
93
- should "only soft-delete itself" do
94
- category = NACategory.create!
95
- category.soft_delete!
96
- assert_deleted category
97
- end
98
- end
99
-
100
- context "with independent associations" do
101
- should "not delete associations" do
102
- category = IDACategory.create!
103
- forum = category.forums.create!
104
- category.soft_delete!
105
- assert_deleted forum
106
- end
107
- end
108
-
109
- context "with dependent has_one association" do
110
- setup do
111
- @category = HOACategory.create!
112
- @forum = @category.create_forum
113
- end
114
-
115
- successfully_soft_deletes
116
- successfully_bulk_soft_deletes
117
- end
118
-
119
- context "with dependent association that doesn't have soft deletion" do
120
- setup do
121
- @category = DACategory.create!
122
- @forum = @category.destroyable_forums.create!
123
- end
124
-
125
- context "successfully soft deleted" do
126
- setup do
127
- @category.soft_delete!
128
- end
129
-
130
- should "mark itself as deleted" do
131
- assert_deleted @category
132
- end
133
-
134
- should "not destroy dependent association" do
135
- assert DestroyableForum.exists?(@forum.id)
136
- end
137
- end
138
- end
139
-
140
- context "with dependent has_many associations" do
141
- setup do
142
- @category = Category.create!
143
- @forum = @category.forums.create!
144
- end
145
-
146
- context "failing to soft delete" do
147
- setup do
148
- @category.stubs(:valid?).returns(false)
149
- assert_raise(ActiveRecord::RecordInvalid) { @category.soft_delete! }
150
- end
151
-
152
- should "not mark itself as deleted" do
153
- assert_not_deleted @category
154
- end
155
-
156
- should "not soft delete its dependent associations" do
157
- assert_not_deleted @forum
158
- end
159
- end
160
-
161
- successfully_soft_deletes
162
- successfully_bulk_soft_deletes
163
-
164
- context "being restored from soft deletion" do
165
- setup do
166
- @category.soft_delete!
167
- Category.with_deleted do
168
- @category.reload
169
- @category.soft_undelete!
170
- @category.reload
171
- end
172
- end
173
-
174
- should "not mark itself as deleted" do
175
- assert_not_deleted @category
176
- end
177
-
178
- should "restore its dependent associations" do
179
- assert_not_deleted @forum
180
- end
181
- end
182
- end
183
-
184
- context "a soft-deleted has-many category that nullifies forum references on delete" do
185
- should "nullify those references" do
186
- category = NDACategory.create!
187
- forum = category.forums.create!
188
- category.soft_delete!
189
- assert_deleted forum
190
- #assert_nil forum.category_id # TODO
191
- end
192
- end
193
-
194
- context "without deleted_at column" do
195
- should "default scope should not provoke an error" do
196
- assert_nothing_raised do
197
- OriginalCategory.create!
198
- end
199
- end
200
- end
201
-
202
- context ".soft_delete_all!" do
203
- setup do
204
- @categories = 2.times.map { Category.create! }
205
- end
206
-
207
- context "by id" do
208
- setup do
209
- Category.soft_delete_all!(@categories.map(&:id))
210
- end
211
-
212
- should "delete all models" do
213
- @categories.each do |category|
214
- assert_deleted category
215
- end
216
- end
217
- end
218
-
219
- context "by model" do
220
- setup do
221
- Category.soft_delete_all!(@categories)
222
- end
223
-
224
- should "delete all models" do
225
- @categories.each do |category|
226
- assert_deleted category
227
- end
228
- end
229
- end
230
- end
231
-
232
- context "overwritten default scope" do
233
- should "find even with deleted_at" do
234
- forum = Cat1Forum.create(:deleted_at => Time.now)
235
- assert Cat1Forum.find_by_id(forum.id)
236
- end
237
-
238
- should "not find by new scope" do
239
- # create! does not work here on rails 2
240
- forum = Cat1Forum.new
241
- forum.category_id = 2
242
- forum.save!
243
- assert_nil Cat1Forum.find_by_id(forum.id)
244
- end
245
- end
246
-
247
- context "validations" do
248
- should "fail when validations fail" do
249
- forum = ValidatedForum.create!(:category_id => 1)
250
- forum.category_id = nil
251
- assert_raise ActiveRecord::RecordInvalid do
252
- forum.soft_delete!
253
- end
254
- assert_not_deleted forum
255
- end
256
-
257
- should "pass when validations pass" do
258
- forum = ValidatedForum.create!(:category_id => 1)
259
- forum.soft_delete!
260
- assert_deleted forum
261
- end
262
- end
263
-
264
- context "#soft_delete" do
265
- should "return true if it succeeds" do
266
- forum = ValidatedForum.create!(:category_id => 1)
267
- assert_equal true, forum.soft_delete
268
- assert_deleted forum
269
- end
270
-
271
- should "return false if validations fail" do
272
- forum = ValidatedForum.create!(:category_id => 1)
273
- forum.category_id = nil
274
- assert_equal false, forum.soft_delete
275
- assert_not_deleted forum
276
- end
277
-
278
- should "return true if validations are prevented and it succeeds" do
279
- forum = ValidatedForum.create!(:category_id => 1)
280
- forum.category_id = nil
281
- skip_validations = (ActiveRecord::VERSION::MAJOR == 2 ? false : {:validate => false})
282
- assert_equal true, forum.soft_delete(skip_validations)
283
- assert_deleted forum
284
- end
285
- end
286
- end