cmoran92-paranoia 2.0.2a
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 +6 -0
- data/.travis.yml +10 -0
- data/Gemfile +17 -0
- data/LICENSE +17 -0
- data/README.md +208 -0
- data/Rakefile +10 -0
- data/lib/paranoia/rspec.rb +13 -0
- data/lib/paranoia/version.rb +3 -0
- data/lib/paranoia.rb +227 -0
- data/paranoia.gemspec +25 -0
- data/test/paranoia_test.rb +760 -0
- metadata +125 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
3
|
+
test_framework = if ActiveRecord::VERSION::STRING >= "4.1"
|
|
4
|
+
require 'minitest/autorun'
|
|
5
|
+
MiniTest::Test
|
|
6
|
+
else
|
|
7
|
+
require 'test/unit'
|
|
8
|
+
Test::Unit::TestCase
|
|
9
|
+
end
|
|
10
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/paranoia")
|
|
11
|
+
|
|
12
|
+
def connect!
|
|
13
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', database: ':memory:'
|
|
14
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE parent_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
15
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)'
|
|
16
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, paranoid_model_with_has_one_id INTEGER)'
|
|
17
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_anthor_class_name_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, paranoid_model_with_has_one_id INTEGER)'
|
|
18
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE paranoid_model_with_foreign_key_belongs (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME, has_one_foreign_key_id INTEGER)'
|
|
19
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE featureful_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME, name VARCHAR(32))'
|
|
20
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE plain_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
21
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
22
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE fail_callback_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
23
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER NOT NULL, deleted_at DATETIME)'
|
|
24
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE asplode_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER, deleted_at DATETIME)'
|
|
25
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
26
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
27
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE jobs (id INTEGER NOT NULL PRIMARY KEY, employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME)'
|
|
28
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)'
|
|
29
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE custom_sentinel_models (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME NOT NULL)'
|
|
30
|
+
ActiveRecord::Base.connection.execute 'CREATE TABLE non_paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER)'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class WithDifferentConnection < ActiveRecord::Base
|
|
34
|
+
establish_connection adapter: 'sqlite3', database: ':memory:'
|
|
35
|
+
connection.execute 'CREATE TABLE with_different_connections (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
|
|
36
|
+
acts_as_paranoid
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
connect!
|
|
40
|
+
|
|
41
|
+
class ParanoiaTest < test_framework
|
|
42
|
+
def setup
|
|
43
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
|
44
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_plain_model_class_is_not_paranoid
|
|
49
|
+
assert_equal false, PlainModel.paranoid?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_paranoid_model_class_is_paranoid
|
|
53
|
+
assert_equal true, ParanoidModel.paranoid?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_plain_models_are_not_paranoid
|
|
57
|
+
assert_equal false, PlainModel.new.paranoid?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_paranoid_models_are_paranoid
|
|
61
|
+
assert_equal true, ParanoidModel.new.paranoid?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_paranoid_models_to_param
|
|
65
|
+
model = ParanoidModel.new
|
|
66
|
+
model.save
|
|
67
|
+
to_param = model.to_param
|
|
68
|
+
|
|
69
|
+
model.destroy
|
|
70
|
+
|
|
71
|
+
assert model.to_param
|
|
72
|
+
assert_equal to_param, model.to_param
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_destroy_behavior_for_plain_models
|
|
76
|
+
model = PlainModel.new
|
|
77
|
+
assert_equal 0, model.class.count
|
|
78
|
+
model.save!
|
|
79
|
+
assert_equal 1, model.class.count
|
|
80
|
+
model.destroy
|
|
81
|
+
|
|
82
|
+
assert_equal true, model.deleted_at.nil?
|
|
83
|
+
|
|
84
|
+
assert_equal 0, model.class.count
|
|
85
|
+
assert_equal 0, model.class.unscoped.count
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Anti-regression test for #81, which would've introduced a bug to break this test.
|
|
89
|
+
def test_destroy_behavior_for_plain_models_callbacks
|
|
90
|
+
model = CallbackModel.new
|
|
91
|
+
model.save
|
|
92
|
+
model.remove_called_variables # clear called callback flags
|
|
93
|
+
model.destroy
|
|
94
|
+
|
|
95
|
+
assert_equal nil, model.instance_variable_get(:@update_callback_called)
|
|
96
|
+
assert_equal nil, model.instance_variable_get(:@save_callback_called)
|
|
97
|
+
assert_equal nil, model.instance_variable_get(:@validate_called)
|
|
98
|
+
|
|
99
|
+
assert model.instance_variable_get(:@destroy_callback_called)
|
|
100
|
+
assert model.instance_variable_get(:@after_destroy_callback_called)
|
|
101
|
+
assert model.instance_variable_get(:@after_commit_callback_called)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_delete_behavior_for_plain_models_callbacks
|
|
106
|
+
model = CallbackModel.new
|
|
107
|
+
model.save
|
|
108
|
+
model.remove_called_variables # clear called callback flags
|
|
109
|
+
model.delete
|
|
110
|
+
|
|
111
|
+
assert_equal nil, model.instance_variable_get(:@update_callback_called)
|
|
112
|
+
assert_equal nil, model.instance_variable_get(:@save_callback_called)
|
|
113
|
+
assert_equal nil, model.instance_variable_get(:@validate_called)
|
|
114
|
+
assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
|
|
115
|
+
assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called)
|
|
116
|
+
assert model.instance_variable_get(:@after_commit_callback_called)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_destroy_behavior_for_paranoid_models
|
|
120
|
+
model = ParanoidModel.new
|
|
121
|
+
assert_equal 0, model.class.count
|
|
122
|
+
model.save!
|
|
123
|
+
assert_equal 1, model.class.count
|
|
124
|
+
model.destroy
|
|
125
|
+
|
|
126
|
+
assert_equal false, model.deleted_at.nil?
|
|
127
|
+
|
|
128
|
+
assert_equal 0, model.class.count
|
|
129
|
+
assert_equal 1, model.class.unscoped.count
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_scoping_behavior_for_paranoid_models
|
|
133
|
+
parent1 = ParentModel.create
|
|
134
|
+
parent2 = ParentModel.create
|
|
135
|
+
p1 = ParanoidModel.create(:parent_model => parent1)
|
|
136
|
+
p2 = ParanoidModel.create(:parent_model => parent2)
|
|
137
|
+
p1.destroy
|
|
138
|
+
p2.destroy
|
|
139
|
+
assert_equal 0, parent1.paranoid_models.count
|
|
140
|
+
assert_equal 1, parent1.paranoid_models.only_deleted.count
|
|
141
|
+
assert_equal 1, parent1.paranoid_models.deleted.count
|
|
142
|
+
p3 = ParanoidModel.create(:parent_model => parent1)
|
|
143
|
+
assert_equal 2, parent1.paranoid_models.with_deleted.count
|
|
144
|
+
assert_equal [p1,p3], parent1.paranoid_models.with_deleted
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_destroy_behavior_for_custom_column_models
|
|
148
|
+
model = CustomColumnModel.new
|
|
149
|
+
assert_equal 0, model.class.count
|
|
150
|
+
model.save!
|
|
151
|
+
assert_nil model.destroyed_at
|
|
152
|
+
assert_equal 1, model.class.count
|
|
153
|
+
model.destroy
|
|
154
|
+
|
|
155
|
+
assert_equal false, model.destroyed_at.nil?
|
|
156
|
+
assert model.destroyed?
|
|
157
|
+
|
|
158
|
+
assert_equal 0, model.class.count
|
|
159
|
+
assert_equal 1, model.class.unscoped.count
|
|
160
|
+
assert_equal 1, model.class.only_deleted.count
|
|
161
|
+
assert_equal 1, model.class.deleted.count
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_default_sentinel_value
|
|
165
|
+
assert_equal nil, ParanoidModel.paranoia_sentinel_value
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_sentinel_value_for_custom_sentinel_models
|
|
169
|
+
model = CustomSentinelModel.new
|
|
170
|
+
assert_equal 0, model.class.count
|
|
171
|
+
model.save!
|
|
172
|
+
assert_equal DateTime.new(0), model.deleted_at
|
|
173
|
+
assert_equal 1, model.class.count
|
|
174
|
+
model.destroy
|
|
175
|
+
|
|
176
|
+
assert DateTime.new(0) != model.deleted_at
|
|
177
|
+
assert model.destroyed?
|
|
178
|
+
|
|
179
|
+
assert_equal 0, model.class.count
|
|
180
|
+
assert_equal 1, model.class.unscoped.count
|
|
181
|
+
assert_equal 1, model.class.only_deleted.count
|
|
182
|
+
assert_equal 1, model.class.deleted.count
|
|
183
|
+
|
|
184
|
+
model.restore
|
|
185
|
+
assert_equal DateTime.new(0), model.deleted_at
|
|
186
|
+
assert !model.destroyed?
|
|
187
|
+
|
|
188
|
+
assert_equal 1, model.class.count
|
|
189
|
+
assert_equal 1, model.class.unscoped.count
|
|
190
|
+
assert_equal 0, model.class.only_deleted.count
|
|
191
|
+
assert_equal 0, model.class.deleted.count
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_destroy_behavior_for_featureful_paranoid_models
|
|
195
|
+
model = get_featureful_model
|
|
196
|
+
assert_equal 0, model.class.count
|
|
197
|
+
model.save!
|
|
198
|
+
assert_equal 1, model.class.count
|
|
199
|
+
model.destroy
|
|
200
|
+
|
|
201
|
+
assert_equal false, model.deleted_at.nil?
|
|
202
|
+
|
|
203
|
+
assert_equal 0, model.class.count
|
|
204
|
+
assert_equal 1, model.class.unscoped.count
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Regression test for #24
|
|
208
|
+
def test_chaining_for_paranoid_models
|
|
209
|
+
scope = FeaturefulModel.where(:name => "foo").only_deleted
|
|
210
|
+
assert_equal "foo", scope.where_values_hash['name']
|
|
211
|
+
assert_equal 2, scope.where_values.count
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_only_destroyed_scope_for_paranoid_models
|
|
215
|
+
model = ParanoidModel.new
|
|
216
|
+
model.save
|
|
217
|
+
model.destroy
|
|
218
|
+
model2 = ParanoidModel.new
|
|
219
|
+
model2.save
|
|
220
|
+
|
|
221
|
+
assert_equal model, ParanoidModel.only_deleted.last
|
|
222
|
+
assert_equal false, ParanoidModel.only_deleted.include?(model2)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def test_default_scope_for_has_many_relationships
|
|
226
|
+
parent = ParentModel.create
|
|
227
|
+
assert_equal 0, parent.related_models.count
|
|
228
|
+
|
|
229
|
+
child = parent.related_models.create
|
|
230
|
+
assert_equal 1, parent.related_models.count
|
|
231
|
+
|
|
232
|
+
child.destroy
|
|
233
|
+
assert_equal false, child.deleted_at.nil?
|
|
234
|
+
|
|
235
|
+
assert_equal 0, parent.related_models.count
|
|
236
|
+
assert_equal 1, parent.related_models.unscoped.count
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_default_scope_for_has_many_through_relationships
|
|
240
|
+
employer = Employer.create
|
|
241
|
+
employee = Employee.create
|
|
242
|
+
assert_equal 0, employer.jobs.count
|
|
243
|
+
assert_equal 0, employer.employees.count
|
|
244
|
+
assert_equal 0, employee.jobs.count
|
|
245
|
+
assert_equal 0, employee.employers.count
|
|
246
|
+
|
|
247
|
+
job = Job.create :employer => employer, :employee => employee
|
|
248
|
+
assert_equal 1, employer.jobs.count
|
|
249
|
+
assert_equal 1, employer.employees.count
|
|
250
|
+
assert_equal 1, employee.jobs.count
|
|
251
|
+
assert_equal 1, employee.employers.count
|
|
252
|
+
|
|
253
|
+
employee2 = Employee.create
|
|
254
|
+
job2 = Job.create :employer => employer, :employee => employee2
|
|
255
|
+
employee2.destroy
|
|
256
|
+
assert_equal 2, employer.jobs.count
|
|
257
|
+
assert_equal 1, employer.employees.count
|
|
258
|
+
|
|
259
|
+
job.destroy
|
|
260
|
+
assert_equal 1, employer.jobs.count
|
|
261
|
+
assert_equal 0, employer.employees.count
|
|
262
|
+
assert_equal 0, employee.jobs.count
|
|
263
|
+
assert_equal 0, employee.employers.count
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def test_delete_behavior_for_callbacks
|
|
267
|
+
model = CallbackModel.new
|
|
268
|
+
model.save
|
|
269
|
+
model.delete
|
|
270
|
+
assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def test_destroy_behavior_for_callbacks
|
|
274
|
+
model = CallbackModel.new
|
|
275
|
+
model.save
|
|
276
|
+
model.destroy
|
|
277
|
+
assert model.instance_variable_get(:@destroy_callback_called)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def test_restore
|
|
281
|
+
model = ParanoidModel.new
|
|
282
|
+
model.save
|
|
283
|
+
id = model.id
|
|
284
|
+
model.destroy
|
|
285
|
+
|
|
286
|
+
assert model.destroyed?
|
|
287
|
+
|
|
288
|
+
model = ParanoidModel.only_deleted.find(id)
|
|
289
|
+
model.restore!
|
|
290
|
+
model.reload
|
|
291
|
+
|
|
292
|
+
assert_equal false, model.destroyed?
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def test_restore_on_object_return_self
|
|
296
|
+
model = ParanoidModel.create
|
|
297
|
+
model.destroy
|
|
298
|
+
|
|
299
|
+
assert_equal model.class, model.restore.class
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Regression test for #92
|
|
303
|
+
def test_destroy_twice
|
|
304
|
+
model = ParanoidModel.new
|
|
305
|
+
model.save
|
|
306
|
+
model.destroy
|
|
307
|
+
model.destroy
|
|
308
|
+
|
|
309
|
+
assert_equal 1, ParanoidModel.unscoped.where(id: model.id).count
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def test_destroy_return_value_on_success
|
|
313
|
+
model = ParanoidModel.create
|
|
314
|
+
return_value = model.destroy
|
|
315
|
+
|
|
316
|
+
assert_equal(return_value, model)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def test_destroy_return_value_on_failure
|
|
320
|
+
model = FailCallbackModel.create
|
|
321
|
+
return_value = model.destroy
|
|
322
|
+
|
|
323
|
+
assert_equal(return_value, false)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def test_restore_behavior_for_callbacks
|
|
327
|
+
model = CallbackModel.new
|
|
328
|
+
model.save
|
|
329
|
+
id = model.id
|
|
330
|
+
model.destroy
|
|
331
|
+
|
|
332
|
+
assert model.destroyed?
|
|
333
|
+
|
|
334
|
+
model = CallbackModel.only_deleted.find(id)
|
|
335
|
+
model.restore!
|
|
336
|
+
model.reload
|
|
337
|
+
|
|
338
|
+
assert model.instance_variable_get(:@restore_callback_called)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def test_really_destroy
|
|
342
|
+
model = ParanoidModel.new
|
|
343
|
+
model.save
|
|
344
|
+
model.really_destroy!
|
|
345
|
+
refute ParanoidModel.unscoped.exists?(model.id)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def test_real_destroy_dependent_destroy
|
|
349
|
+
parent = ParentModel.create
|
|
350
|
+
child = parent.very_related_models.create
|
|
351
|
+
parent.really_destroy!
|
|
352
|
+
refute RelatedModel.unscoped.exists?(child.id)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def test_real_destroy_dependent_destroy_after_normal_destroy
|
|
356
|
+
parent = ParentModel.create
|
|
357
|
+
child = parent.very_related_models.create
|
|
358
|
+
parent.destroy
|
|
359
|
+
parent.really_destroy!
|
|
360
|
+
refute RelatedModel.unscoped.exists?(child.id)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def test_real_destroy_dependent_destroy_after_normal_destroy_does_not_delete_other_children
|
|
364
|
+
parent_1 = ParentModel.create
|
|
365
|
+
child_1 = parent_1.very_related_models.create
|
|
366
|
+
|
|
367
|
+
parent_2 = ParentModel.create
|
|
368
|
+
child_2 = parent_2.very_related_models.create
|
|
369
|
+
parent_1.destroy
|
|
370
|
+
parent_1.really_destroy!
|
|
371
|
+
assert RelatedModel.unscoped.exists?(child_2.id)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
if ActiveRecord::VERSION::STRING < "4.1"
|
|
375
|
+
def test_real_destroy
|
|
376
|
+
model = ParanoidModel.new
|
|
377
|
+
model.save
|
|
378
|
+
model.destroy!
|
|
379
|
+
refute ParanoidModel.unscoped.exists?(model.id)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def test_real_delete
|
|
384
|
+
model = ParanoidModel.new
|
|
385
|
+
model.save
|
|
386
|
+
model.delete!
|
|
387
|
+
|
|
388
|
+
refute ParanoidModel.unscoped.exists?(model.id)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def test_multiple_restore
|
|
392
|
+
a = ParanoidModel.new
|
|
393
|
+
a.save
|
|
394
|
+
a_id = a.id
|
|
395
|
+
a.destroy
|
|
396
|
+
|
|
397
|
+
b = ParanoidModel.new
|
|
398
|
+
b.save
|
|
399
|
+
b_id = b.id
|
|
400
|
+
b.destroy
|
|
401
|
+
|
|
402
|
+
c = ParanoidModel.new
|
|
403
|
+
c.save
|
|
404
|
+
c_id = c.id
|
|
405
|
+
c.destroy
|
|
406
|
+
|
|
407
|
+
ParanoidModel.restore([a_id, c_id])
|
|
408
|
+
|
|
409
|
+
a.reload
|
|
410
|
+
b.reload
|
|
411
|
+
c.reload
|
|
412
|
+
|
|
413
|
+
refute a.destroyed?
|
|
414
|
+
assert b.destroyed?
|
|
415
|
+
refute c.destroyed?
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def test_restore_with_associations
|
|
419
|
+
parent = ParentModel.create
|
|
420
|
+
first_child = parent.very_related_models.create
|
|
421
|
+
second_child = parent.non_paranoid_models.create
|
|
422
|
+
|
|
423
|
+
parent.destroy
|
|
424
|
+
assert_equal false, parent.deleted_at.nil?
|
|
425
|
+
assert_equal false, first_child.reload.deleted_at.nil?
|
|
426
|
+
assert_equal true, second_child.destroyed?
|
|
427
|
+
|
|
428
|
+
parent.restore!
|
|
429
|
+
assert_equal true, parent.deleted_at.nil?
|
|
430
|
+
assert_equal false, first_child.reload.deleted_at.nil?
|
|
431
|
+
assert_equal true, second_child.destroyed?
|
|
432
|
+
|
|
433
|
+
parent.destroy
|
|
434
|
+
parent.restore(:recursive => true)
|
|
435
|
+
assert_equal true, parent.deleted_at.nil?
|
|
436
|
+
assert_equal true, first_child.reload.deleted_at.nil?
|
|
437
|
+
assert_equal true, second_child.destroyed?
|
|
438
|
+
|
|
439
|
+
parent.destroy
|
|
440
|
+
ParentModel.restore(parent.id, :recursive => true)
|
|
441
|
+
assert_equal true, parent.reload.deleted_at.nil?
|
|
442
|
+
assert_equal true, first_child.reload.deleted_at.nil?
|
|
443
|
+
assert_equal true, second_child.destroyed?
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# regression tests for #118
|
|
447
|
+
def test_restore_with_has_one_association
|
|
448
|
+
# setup and destroy test objects
|
|
449
|
+
hasOne = ParanoidModelWithHasOne.create
|
|
450
|
+
belongsTo = ParanoidModelWithBelong.create
|
|
451
|
+
anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
|
|
452
|
+
foreignKey = ParanoidModelWithForeignKeyBelong.create
|
|
453
|
+
hasOne.paranoid_model_with_belong = belongsTo
|
|
454
|
+
hasOne.class_name_belong = anthorClassName
|
|
455
|
+
hasOne.paranoid_model_with_foreign_key_belong = foreignKey
|
|
456
|
+
hasOne.save!
|
|
457
|
+
|
|
458
|
+
hasOne.destroy
|
|
459
|
+
assert_equal false, hasOne.deleted_at.nil?
|
|
460
|
+
assert_equal false, belongsTo.deleted_at.nil?
|
|
461
|
+
|
|
462
|
+
# Does it restore has_one associations?
|
|
463
|
+
hasOne.restore(:recursive => true)
|
|
464
|
+
hasOne.save!
|
|
465
|
+
|
|
466
|
+
assert_equal true, hasOne.reload.deleted_at.nil?
|
|
467
|
+
assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
|
|
468
|
+
assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
|
|
469
|
+
assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
|
|
470
|
+
assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def test_new_restore_with_has_one_association
|
|
474
|
+
# setup and destroy test objects
|
|
475
|
+
hasOne = ParanoidModelWithHasOne.create
|
|
476
|
+
belongsTo = ParanoidModelWithBelong.create
|
|
477
|
+
anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
|
|
478
|
+
foreignKey = ParanoidModelWithForeignKeyBelong.create
|
|
479
|
+
hasOne.paranoid_model_with_belong = belongsTo
|
|
480
|
+
hasOne.class_name_belong = anthorClassName
|
|
481
|
+
hasOne.paranoid_model_with_foreign_key_belong = foreignKey
|
|
482
|
+
hasOne.save!
|
|
483
|
+
|
|
484
|
+
hasOne.destroy
|
|
485
|
+
assert_equal false, hasOne.deleted_at.nil?
|
|
486
|
+
assert_equal false, belongsTo.deleted_at.nil?
|
|
487
|
+
|
|
488
|
+
# Does it restore has_one associations?
|
|
489
|
+
newHasOne = ParanoidModelWithHasOne.with_deleted.find(hasOne.id)
|
|
490
|
+
newHasOne.restore(:recursive => true)
|
|
491
|
+
newHasOne.save!
|
|
492
|
+
|
|
493
|
+
assert_equal true, hasOne.reload.deleted_at.nil?
|
|
494
|
+
assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
|
|
495
|
+
assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
|
|
496
|
+
assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
|
|
497
|
+
assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def test_model_restore_with_has_one_association
|
|
501
|
+
# setup and destroy test objects
|
|
502
|
+
hasOne = ParanoidModelWithHasOne.create
|
|
503
|
+
belongsTo = ParanoidModelWithBelong.create
|
|
504
|
+
anthorClassName = ParanoidModelWithAnthorClassNameBelong.create
|
|
505
|
+
foreignKey = ParanoidModelWithForeignKeyBelong.create
|
|
506
|
+
hasOne.paranoid_model_with_belong = belongsTo
|
|
507
|
+
hasOne.class_name_belong = anthorClassName
|
|
508
|
+
hasOne.paranoid_model_with_foreign_key_belong = foreignKey
|
|
509
|
+
hasOne.save!
|
|
510
|
+
|
|
511
|
+
hasOne.destroy
|
|
512
|
+
assert_equal false, hasOne.deleted_at.nil?
|
|
513
|
+
assert_equal false, belongsTo.deleted_at.nil?
|
|
514
|
+
|
|
515
|
+
# Does it restore has_one associations?
|
|
516
|
+
ParanoidModelWithHasOne.restore(hasOne.id, :recursive => true)
|
|
517
|
+
hasOne.save!
|
|
518
|
+
|
|
519
|
+
assert_equal true, hasOne.reload.deleted_at.nil?
|
|
520
|
+
assert_equal true, belongsTo.reload.deleted_at.nil?, "#{belongsTo.deleted_at}"
|
|
521
|
+
assert ParanoidModelWithBelong.with_deleted.reload.count != 0, "There should be a record"
|
|
522
|
+
assert ParanoidModelWithAnthorClassNameBelong.with_deleted.reload.count != 0, "There should be an other record"
|
|
523
|
+
assert ParanoidModelWithForeignKeyBelong.with_deleted.reload.count != 0, "There should be a foreign_key record"
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def test_restore_with_nil_has_one_association
|
|
527
|
+
# setup and destroy test object
|
|
528
|
+
hasOne = ParanoidModelWithHasOne.create
|
|
529
|
+
hasOne.destroy
|
|
530
|
+
assert_equal false, hasOne.reload.deleted_at.nil?
|
|
531
|
+
|
|
532
|
+
# Does it raise NoMethodException on restore of nil
|
|
533
|
+
hasOne.restore(:recursive => true)
|
|
534
|
+
|
|
535
|
+
assert hasOne.reload.deleted_at.nil?
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# covers #131
|
|
539
|
+
def test_has_one_really_destroy_with_nil
|
|
540
|
+
model = ParanoidModelWithHasOne.create
|
|
541
|
+
model.really_destroy!
|
|
542
|
+
|
|
543
|
+
refute ParanoidModelWithBelong.unscoped.exists?(model.id)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def test_has_one_really_destroy_with_record
|
|
547
|
+
model = ParanoidModelWithHasOne.create { |record| record.build_paranoid_model_with_belong }
|
|
548
|
+
model.really_destroy!
|
|
549
|
+
|
|
550
|
+
refute ParanoidModelWithBelong.unscoped.exists?(model.id)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def test_observers_notified
|
|
554
|
+
a = ParanoidModelWithObservers.create
|
|
555
|
+
a.destroy
|
|
556
|
+
a.restore!
|
|
557
|
+
|
|
558
|
+
assert a.observers_notified.select {|args| args == [:before_restore, a]}
|
|
559
|
+
assert a.observers_notified.select {|args| args == [:after_restore, a]}
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def test_observers_not_notified_if_not_supported
|
|
563
|
+
a = ParanoidModelWithObservers.create
|
|
564
|
+
a.destroy
|
|
565
|
+
a.restore!
|
|
566
|
+
# essentially, we're just ensuring that this doesn't crash
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def test_i_am_the_destroyer
|
|
570
|
+
output = capture(:stdout) { ParanoidModel.I_AM_THE_DESTROYER! }
|
|
571
|
+
assert_equal %Q{
|
|
572
|
+
Sharon: "There should be a method called I_AM_THE_DESTROYER!"
|
|
573
|
+
Ryan: "What should this method do?"
|
|
574
|
+
Sharon: "It should fix all the spelling errors on the page!"
|
|
575
|
+
}, output
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def test_destroy_fails_if_callback_raises_exception
|
|
579
|
+
parent = AsplodeModel.create
|
|
580
|
+
|
|
581
|
+
assert_raises(StandardError) { parent.destroy }
|
|
582
|
+
|
|
583
|
+
#transaction should be rolled back, so parent NOT deleted
|
|
584
|
+
refute parent.destroyed?, 'Parent record was destroyed, even though AR callback threw exception'
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def test_destroy_fails_if_association_callback_raises_exception
|
|
588
|
+
parent = ParentModel.create
|
|
589
|
+
children = []
|
|
590
|
+
3.times { children << parent.asplode_models.create }
|
|
591
|
+
|
|
592
|
+
assert_raises(StandardError) { parent.destroy }
|
|
593
|
+
|
|
594
|
+
#transaction should be rolled back, so parent and children NOT deleted
|
|
595
|
+
refute parent.destroyed?, 'Parent record was destroyed, even though AR callback threw exception'
|
|
596
|
+
refute children.any?(&:destroyed?), 'Child record was destroyed, even though AR callback threw exception'
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def test_restore_model_with_different_connection
|
|
600
|
+
ActiveRecord::Base.remove_connection # Disconnect the main connection
|
|
601
|
+
a = WithDifferentConnection.create
|
|
602
|
+
a.destroy!
|
|
603
|
+
a.restore!
|
|
604
|
+
# This test passes if no exception is raised
|
|
605
|
+
connect! # Reconnect the main connection
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def test_restore_clear_association_cache_if_associations_present
|
|
609
|
+
parent = ParentModel.create
|
|
610
|
+
3.times { parent.very_related_models.create }
|
|
611
|
+
|
|
612
|
+
parent.destroy
|
|
613
|
+
|
|
614
|
+
assert_equal 0, parent.very_related_models.count
|
|
615
|
+
assert_equal 0, parent.very_related_models.size
|
|
616
|
+
|
|
617
|
+
parent.restore(recursive: true)
|
|
618
|
+
|
|
619
|
+
assert_equal 3, parent.very_related_models.count
|
|
620
|
+
assert_equal 3, parent.very_related_models.size
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
private
|
|
624
|
+
def get_featureful_model
|
|
625
|
+
FeaturefulModel.new(:name => "not empty")
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Helper classes
|
|
630
|
+
|
|
631
|
+
class ParanoidModel < ActiveRecord::Base
|
|
632
|
+
belongs_to :parent_model
|
|
633
|
+
acts_as_paranoid
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
class FailCallbackModel < ActiveRecord::Base
|
|
637
|
+
belongs_to :parent_model
|
|
638
|
+
acts_as_paranoid
|
|
639
|
+
|
|
640
|
+
before_destroy { |_| false }
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
class FeaturefulModel < ActiveRecord::Base
|
|
644
|
+
acts_as_paranoid
|
|
645
|
+
validates :name, :presence => true, :uniqueness => true
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
class PlainModel < ActiveRecord::Base
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
class CallbackModel < ActiveRecord::Base
|
|
652
|
+
acts_as_paranoid
|
|
653
|
+
before_destroy {|model| model.instance_variable_set :@destroy_callback_called, true }
|
|
654
|
+
before_restore {|model| model.instance_variable_set :@restore_callback_called, true }
|
|
655
|
+
before_update {|model| model.instance_variable_set :@update_callback_called, true }
|
|
656
|
+
before_save {|model| model.instance_variable_set :@save_callback_called, true}
|
|
657
|
+
|
|
658
|
+
after_destroy {|model| model.instance_variable_set :@after_destroy_callback_called, true }
|
|
659
|
+
after_commit {|model| model.instance_variable_set :@after_commit_callback_called, true }
|
|
660
|
+
|
|
661
|
+
validate {|model| model.instance_variable_set :@validate_called, true }
|
|
662
|
+
|
|
663
|
+
def remove_called_variables
|
|
664
|
+
instance_variables.each {|name| (name.to_s.end_with?('_called')) ? remove_instance_variable(name) : nil}
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
class ParentModel < ActiveRecord::Base
|
|
669
|
+
acts_as_paranoid
|
|
670
|
+
has_many :paranoid_models
|
|
671
|
+
has_many :related_models
|
|
672
|
+
has_many :very_related_models, :class_name => 'RelatedModel', dependent: :destroy
|
|
673
|
+
has_many :non_paranoid_models, dependent: :destroy
|
|
674
|
+
has_many :asplode_models, dependent: :destroy
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
class RelatedModel < ActiveRecord::Base
|
|
678
|
+
acts_as_paranoid
|
|
679
|
+
belongs_to :parent_model
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
class Employer < ActiveRecord::Base
|
|
683
|
+
acts_as_paranoid
|
|
684
|
+
has_many :jobs
|
|
685
|
+
has_many :employees, :through => :jobs
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
class Employee < ActiveRecord::Base
|
|
689
|
+
acts_as_paranoid
|
|
690
|
+
has_many :jobs
|
|
691
|
+
has_many :employers, :through => :jobs
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
class Job < ActiveRecord::Base
|
|
695
|
+
acts_as_paranoid
|
|
696
|
+
belongs_to :employer
|
|
697
|
+
belongs_to :employee
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
class CustomColumnModel < ActiveRecord::Base
|
|
701
|
+
acts_as_paranoid column: :destroyed_at
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
class CustomSentinelModel < ActiveRecord::Base
|
|
705
|
+
acts_as_paranoid sentinel_value: DateTime.new(0)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
class NonParanoidModel < ActiveRecord::Base
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
class ParanoidModelWithObservers < ParanoidModel
|
|
712
|
+
def observers_notified
|
|
713
|
+
@observers_notified ||= []
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def self.notify_observer(*args)
|
|
717
|
+
observers_notified << args
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
class ParanoidModelWithoutObservers < ParanoidModel
|
|
722
|
+
self.class.send(remove_method :notify_observers) if method_defined?(:notify_observers)
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# refer back to regression test for #118
|
|
726
|
+
class ParanoidModelWithHasOne < ParanoidModel
|
|
727
|
+
has_one :paranoid_model_with_belong, :dependent => :destroy
|
|
728
|
+
has_one :class_name_belong, :dependent => :destroy, :class_name => "ParanoidModelWithAnthorClassNameBelong"
|
|
729
|
+
has_one :paranoid_model_with_foreign_key_belong, :dependent => :destroy, :foreign_key => "has_one_foreign_key_id"
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
class ParanoidModelWithBelong < ActiveRecord::Base
|
|
733
|
+
acts_as_paranoid
|
|
734
|
+
belongs_to :paranoid_model_with_has_one
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
class ParanoidModelWithAnthorClassNameBelong < ActiveRecord::Base
|
|
738
|
+
acts_as_paranoid
|
|
739
|
+
belongs_to :paranoid_model_with_has_one
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
class ParanoidModelWithForeignKeyBelong < ActiveRecord::Base
|
|
743
|
+
acts_as_paranoid
|
|
744
|
+
belongs_to :paranoid_model_with_has_one
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
class FlaggedModel < PlainModel
|
|
748
|
+
acts_as_paranoid :flag_column => :is_deleted
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
class FlaggedModelWithCustomIndex < PlainModel
|
|
752
|
+
acts_as_paranoid :flag_column => :is_deleted, :indexed_column => :is_deleted
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
class AsplodeModel < ActiveRecord::Base
|
|
756
|
+
acts_as_paranoid
|
|
757
|
+
before_destroy do |r|
|
|
758
|
+
raise StandardError, 'ASPLODE!'
|
|
759
|
+
end
|
|
760
|
+
end
|