soft_deletion 0.1.10 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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