paranoia 1.3.2 → 1.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74da60de8afe27c56c43f9a2ad74eec282f1d0d7
4
- data.tar.gz: fc3b097f03211f08ba1ad711e82d7c6ef20231ab
3
+ metadata.gz: 303721b73429fa8bacda1346701858f2439c8069
4
+ data.tar.gz: 789252f10b981c0260f18f06adba4798fe7dbc99
5
5
  SHA512:
6
- metadata.gz: 1b98a9b12106d88e02d0d059143b56d7fe63dfa12d0362a83da656aef59b5940e5e9e9514caa2c6f51881f2ad306e76977d75efc22a8dcf60a2df04bebe931ce
7
- data.tar.gz: 29a1b849c690b46339e75e5bdd2761bd6f070025c55f543abc7603a1104ebb0feb59b4cc04d70d5ed363f2f38a04de50fc630c5a26d8e31ae1b61287f73d56e4
6
+ metadata.gz: b9b84fe3194f415bf9871274646264d3aa68542310ee46e1abe9ff76f5c9c555cf6bf37f3e7aab09976c248b1456c9633af07c93a437b6e24f92faa29746198a
7
+ data.tar.gz: 22d720ab4dded670a75abc090fc098755526c7cec719515f053f5e94881c3d519286a285d7cadf8f77833ddac34ec4b13316b4d38d6dffa989b691b470ed70bb
@@ -1,4 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
- - 2.0.0
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - jruby-19mode
7
+ - rbx
data/Gemfile CHANGED
@@ -1,4 +1,13 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'sqlite3', :platforms => [:ruby]
4
+ gem 'activerecord-jdbcsqlite3-adapter', :platforms => [:jruby]
5
+
6
+ platforms :rbx do
7
+ gem 'rubysl', '~> 2.0'
8
+ gem 'rubysl-test-unit'
9
+ gem 'rubinius-developer_tools'
10
+ end
11
+
3
12
  # Specify your gem's dependencies in paranoia.gemspec
4
13
  gemspec
data/README.md CHANGED
@@ -6,6 +6,8 @@ You would use either plugin / gem if you wished that when you called `destroy` o
6
6
 
7
7
  If you wish to actually destroy an object you may call destroy! on it or simply call destroy twice on the same object.
8
8
 
9
+ If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted. If they don't have that, then they will not be deleted.
10
+
9
11
  ## Installation & Usage
10
12
 
11
13
  For Rails 3, please use version 1 of Paranoia:
@@ -66,9 +68,23 @@ class Client < ActiveRecord::Base
66
68
  end
67
69
  ```
68
70
 
69
- Hey presto, it's there!
71
+ Hey presto, it's there! Calling `destroy` will now set the `deleted_at` column:
72
+
70
73
 
71
- If you want a method to be called on destroy, simply provide a _before\_destroy_ callback:
74
+ ```
75
+ >> client.deleted_at => nil
76
+ >> client.destroy => client
77
+ >> client.deleted_at => [current timestamp]
78
+ ```
79
+
80
+ If you really want it gone *gone*, call `destroy!`
81
+
82
+ ```
83
+ >> client.deleted_at => nil
84
+ >> client.destroy! => client
85
+ ```
86
+
87
+ If you want a method to be called on destroy, simply provide a `before_destroy` callback:
72
88
 
73
89
  ```ruby
74
90
  class Client < ActiveRecord::Base
@@ -84,6 +100,70 @@ class Client < ActiveRecord::Base
84
100
  end
85
101
  ```
86
102
 
103
+ If you want to use a column other than `deleted_at`, you can pass it as an option:
104
+
105
+ ```ruby
106
+ class Client < ActiveRecord::Base
107
+ acts_as_paranoid column: :destroyed_at
108
+
109
+ ...
110
+ end
111
+ ```
112
+
113
+ If you want to access soft-deleted associations, override the getter method:
114
+
115
+ ```ruby
116
+ def product
117
+ Product.unscoped { super }
118
+ end
119
+ ```
120
+
121
+ If you want to find all records, even those which are deleted:
122
+
123
+ ```ruby
124
+ Client.with_deleted
125
+ ```
126
+
127
+ If you want to find only the deleted records:
128
+
129
+ ```ruby
130
+ Client.only_deleted
131
+ ```
132
+
133
+ If you want to check if a record is soft-deleted:
134
+
135
+ ```ruby
136
+ client.destroyed?
137
+ ```
138
+
139
+ If you want to restore a record:
140
+
141
+ ```ruby
142
+ Client.restore(id)
143
+ ```
144
+
145
+ If you want to restore a whole bunch of records:
146
+
147
+ ```ruby
148
+ Client.restore([id1, id2, ..., idN])
149
+ ```
150
+
151
+ If you want to restore a record and their dependently destroyed associated records:
152
+
153
+ ```ruby
154
+ Client.restore(id, :recursive => true)
155
+ ```
156
+
157
+ If you want callbacks to trigger before a restore:
158
+
159
+ ```ruby
160
+ before_restore :callback_name_goes_here
161
+ ```
162
+
163
+ For more information, please look at the tests.
164
+
165
+ ## Acts As Paranoid Migration
166
+
87
167
  You can replace the older acts_as_paranoid methods as follows:
88
168
 
89
169
  | Old Syntax | New Syntax |
@@ -15,15 +15,15 @@ module Paranoia
15
15
  end
16
16
 
17
17
  def only_deleted
18
- with_deleted.where("#{self.table_name}.deleted_at IS NOT NULL")
18
+ with_deleted.where("#{self.table_name}.#{paranoia_column} IS NOT NULL")
19
19
  end
20
20
  alias :deleted :only_deleted
21
21
 
22
- def restore(id)
22
+ def restore(id, opts = {})
23
23
  if id.is_a?(Array)
24
- id.map { |one_id| restore(one_id) }
24
+ id.map { |one_id| restore(one_id, opts) }
25
25
  else
26
- only_deleted.find(id).restore!
26
+ only_deleted.find(id).restore!(opts)
27
27
  end
28
28
  end
29
29
  end
@@ -47,41 +47,77 @@ module Paranoia
47
47
  end
48
48
 
49
49
  def destroy
50
- run_callbacks(:destroy) { delete }
50
+ run_callbacks(:destroy) { touch_paranoia_column(true) }
51
51
  end
52
52
 
53
53
  def delete
54
54
  return if new_record?
55
- destroyed? ? destroy! : update_attribute_or_column(:deleted_at, Time.now)
55
+ touch_paranoia_column(false)
56
56
  end
57
57
 
58
- def restore!
59
- run_callbacks(:restore) { update_column :deleted_at, nil }
58
+ def restore!(opts = {})
59
+ ActiveRecord::Base.transaction do
60
+ run_callbacks(:restore) do
61
+ update_column paranoia_column, nil
62
+ restore_associated_records if opts[:recursive]
63
+ end
64
+ end
60
65
  end
61
66
  alias :restore :restore!
62
67
 
63
68
  def destroyed?
64
- !!deleted_at
69
+ !!send(paranoia_column)
65
70
  end
66
71
 
67
72
  alias :deleted? :destroyed?
68
73
 
69
74
  private
70
75
 
71
- # Rails 3.1 adds update_column. Rails > 3.2.6 deprecates update_attribute, gone in Rails 4.
72
- def update_attribute_or_column(*args)
73
- self.class.unscoped do
74
- respond_to?(:update_column) ? update_column(*args) : update_attribute(*args)
76
+ # touch paranoia column.
77
+ # insert time to paranoia column.
78
+ # @param with_transaction [Boolean] exec with ActiveRecord Transactions.
79
+ def touch_paranoia_column(with_transaction=false)
80
+ if with_transaction
81
+ with_transaction_returning_status { touch(paranoia_column) }
82
+ else
83
+ touch(paranoia_column)
84
+ end
85
+ end
86
+
87
+ # restore associated records that have been soft deleted when
88
+ # we called #destroy
89
+ def restore_associated_records
90
+ destroyed_associations = self.class.reflect_on_all_associations.select do |association|
91
+ association.options[:dependent] == :destroy
92
+ end
93
+
94
+ destroyed_associations.each do |association|
95
+ association = send(association.name)
96
+
97
+ if association.paranoid?
98
+ association.only_deleted.each { |record| record.restore(:recursive => true) }
99
+ end
75
100
  end
76
101
  end
77
102
  end
78
103
 
79
104
  class ActiveRecord::Base
80
- def self.acts_as_paranoid
81
- alias :destroy! :destroy
105
+ def self.acts_as_paranoid(options={})
106
+ alias :ar_destroy :destroy
107
+ alias :destroy! :ar_destroy
82
108
  alias :delete! :delete
83
109
  include Paranoia
84
- default_scope { where(self.quoted_table_name + '.deleted_at IS NULL') }
110
+ class_attribute :paranoia_column
111
+
112
+ self.paranoia_column = options[:column] || :deleted_at
113
+ default_scope { where(self.quoted_table_name + ".#{paranoia_column} IS NULL") }
114
+
115
+ before_restore {
116
+ self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers)
117
+ }
118
+ after_restore {
119
+ self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers)
120
+ }
85
121
  end
86
122
 
87
123
  def self.paranoid?
@@ -98,4 +134,10 @@ class ActiveRecord::Base
98
134
  def persisted?
99
135
  paranoid? ? !new_record? : super
100
136
  end
137
+
138
+ private
139
+
140
+ def paranoia_column
141
+ self.class.paranoia_column
142
+ end
101
143
  end
@@ -1,3 +1,3 @@
1
1
  module Paranoia
2
- VERSION = '1.3.2'
2
+ VERSION = '1.3.3'
3
3
  end
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
17
17
  s.add_dependency 'activerecord', '~> 3.2'
18
18
 
19
19
  s.add_development_dependency 'bundler', '>= 1.0.0'
20
- s.add_development_dependency 'sqlite3'
21
20
  s.add_development_dependency 'rake'
22
21
 
23
22
  s.files = `git ls-files`.split("\n")
@@ -17,6 +17,8 @@ ActiveRecord::Base.connection.execute 'CREATE TABLE related_models (id INTEGER N
17
17
  ActiveRecord::Base.connection.execute 'CREATE TABLE employers (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
18
18
  ActiveRecord::Base.connection.execute 'CREATE TABLE employees (id INTEGER NOT NULL PRIMARY KEY, deleted_at DATETIME)'
19
19
  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)'
20
+ ActiveRecord::Base.connection.execute 'CREATE TABLE custom_column_models (id INTEGER NOT NULL PRIMARY KEY, destroyed_at DATETIME)'
21
+ ActiveRecord::Base.connection.execute 'CREATE TABLE non_paranoid_models (id INTEGER NOT NULL PRIMARY KEY, parent_model_id INTEGER)'
20
22
 
21
23
  class ParanoiaTest < Test::Unit::TestCase
22
24
  def test_plain_model_class_is_not_paranoid
@@ -59,6 +61,37 @@ class ParanoiaTest < Test::Unit::TestCase
59
61
  assert_equal 0, model.class.unscoped.count
60
62
  end
61
63
 
64
+ # Anti-regression test for #81, which would've introduced a bug to break this test.
65
+ def test_destroy_behavior_for_plain_models_callbacks
66
+ model = CallbackModel.new
67
+ model.save
68
+ model.remove_called_variables # clear called callback flags
69
+ model.destroy
70
+
71
+ assert_equal nil, model.instance_variable_get(:@update_callback_called)
72
+ assert_equal nil, model.instance_variable_get(:@save_callback_called)
73
+ assert_equal nil, model.instance_variable_get(:@validate_called)
74
+
75
+ assert model.instance_variable_get(:@destroy_callback_called)
76
+ assert model.instance_variable_get(:@after_destroy_callback_called)
77
+ assert model.instance_variable_get(:@after_commit_callback_called)
78
+ end
79
+
80
+
81
+ def test_delete_behavior_for_plain_models_callbacks
82
+ model = CallbackModel.new
83
+ model.save
84
+ model.remove_called_variables # clear called callback flags
85
+ model.delete
86
+
87
+ assert_equal nil, model.instance_variable_get(:@update_callback_called)
88
+ assert_equal nil, model.instance_variable_get(:@save_callback_called)
89
+ assert_equal nil, model.instance_variable_get(:@validate_called)
90
+ assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
91
+ assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called)
92
+ assert_equal nil, model.instance_variable_get(:@after_commit_callback_called)
93
+ end
94
+
62
95
  def test_destroy_behavior_for_paranoid_models
63
96
  model = ParanoidModel.new
64
97
  assert_equal 0, model.class.count
@@ -88,6 +121,23 @@ class ParanoiaTest < Test::Unit::TestCase
88
121
  assert_equal [p1, p3], parent1.paranoid_models.with_deleted
89
122
  end
90
123
 
124
+ def test_destroy_behavior_for_custom_column_models
125
+ model = CustomColumnModel.new
126
+ assert_equal 0, model.class.count
127
+ model.save!
128
+ assert_nil model.destroyed_at
129
+ assert_equal 1, model.class.count
130
+ model.destroy
131
+
132
+ assert_equal false, model.destroyed_at.nil?
133
+ assert model.destroyed?
134
+
135
+ assert_equal 0, model.class.count
136
+ assert_equal 1, model.class.unscoped.count
137
+ assert_equal 1, model.class.only_deleted.count
138
+ assert_equal 1, model.class.deleted.count
139
+ end
140
+
91
141
  def test_destroy_behavior_for_featureful_paranoid_models
92
142
  model = get_featureful_model
93
143
  assert_equal 0, model.class.count
@@ -165,14 +215,14 @@ class ParanoiaTest < Test::Unit::TestCase
165
215
  model = CallbackModel.new
166
216
  model.save
167
217
  model.delete
168
- assert_equal nil, model.instance_variable_get(:@callback_called)
218
+ assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
169
219
  end
170
220
 
171
221
  def test_destroy_behavior_for_callbacks
172
222
  model = CallbackModel.new
173
223
  model.save
174
224
  model.destroy
175
- assert model.instance_variable_get(:@callback_called)
225
+ assert model.instance_variable_get(:@destroy_callback_called)
176
226
  end
177
227
 
178
228
  def test_restore
@@ -190,13 +240,14 @@ class ParanoiaTest < Test::Unit::TestCase
190
240
  assert_equal false, model.destroyed?
191
241
  end
192
242
 
243
+ # Regression test for #92
193
244
  def test_destroy_twice
194
245
  model = ParanoidModel.new
195
246
  model.save
196
247
  model.destroy
197
248
  model.destroy
198
249
 
199
- assert_equal 0, ParanoidModel.unscoped.where(id: model.id).count
250
+ assert_equal 1, ParanoidModel.unscoped.where(id: model.id).count
200
251
  end
201
252
 
202
253
  def test_restore_behavior_for_callbacks
@@ -256,6 +307,50 @@ class ParanoiaTest < Test::Unit::TestCase
256
307
  refute c.destroyed?
257
308
  end
258
309
 
310
+ def test_restore_with_associations
311
+ parent = ParentModel.create
312
+ first_child = parent.very_related_models.create
313
+ second_child = parent.non_paranoid_models.create
314
+
315
+ parent.destroy
316
+ assert_equal false, parent.deleted_at.nil?
317
+ assert_equal false, first_child.reload.deleted_at.nil?
318
+ assert_equal true, second_child.destroyed?
319
+
320
+ parent.restore!
321
+ assert_equal true, parent.deleted_at.nil?
322
+ assert_equal false, first_child.reload.deleted_at.nil?
323
+ assert_equal true, second_child.destroyed?
324
+
325
+ parent.destroy
326
+ parent.restore(:recursive => true)
327
+ assert_equal true, parent.deleted_at.nil?
328
+ assert_equal true, first_child.reload.deleted_at.nil?
329
+ assert_equal true, second_child.destroyed?
330
+
331
+ parent.destroy
332
+ ParentModel.restore(parent.id, :recursive => true)
333
+ assert_equal true, parent.reload.deleted_at.nil?
334
+ assert_equal true, first_child.reload.deleted_at.nil?
335
+ assert_equal true, second_child.destroyed?
336
+ end
337
+
338
+ def test_observers_notified
339
+ a = ParanoidModelWithObservers.create
340
+ a.destroy
341
+ a.restore!
342
+
343
+ assert a.observers_notified.select {|args| args == [:before_restore, a]}
344
+ assert a.observers_notified.select {|args| args == [:after_restore, a]}
345
+ end
346
+
347
+ def test_observers_not_notified_if_not_supported
348
+ a = ParanoidModelWithObservers.create
349
+ a.destroy
350
+ a.restore!
351
+ # essentially, we're just ensuring that this doesn't crash
352
+ end
353
+
259
354
  private
260
355
  def get_featureful_model
261
356
  FeaturefulModel.new(:name => 'not empty')
@@ -283,13 +378,26 @@ end
283
378
 
284
379
  class CallbackModel < ActiveRecord::Base
285
380
  acts_as_paranoid
286
- before_destroy {|model| model.instance_variable_set :@callback_called, true }
381
+ before_destroy {|model| model.instance_variable_set :@destroy_callback_called, true }
287
382
  before_restore {|model| model.instance_variable_set :@restore_callback_called, true }
383
+ before_update {|model| model.instance_variable_set :@update_callback_called, true }
384
+ before_save {|model| model.instance_variable_set :@save_callback_called, true}
385
+
386
+ after_destroy {|model| model.instance_variable_set :@after_destroy_callback_called, true }
387
+ after_commit {|model| model.instance_variable_set :@after_commit_callback_called, true }
388
+
389
+ validate {|model| model.instance_variable_set :@validate_called, true }
390
+
391
+ def remove_called_variables
392
+ instance_variables.each {|name| (name.to_s.end_with?('_called')) ? remove_instance_variable(name) : nil}
393
+ end
288
394
  end
289
395
 
290
396
  class ParentModel < ActiveRecord::Base
291
397
  acts_as_paranoid
292
398
  has_many :related_models
399
+ has_many :very_related_models, :class_name => 'RelatedModel', dependent: :destroy
400
+ has_many :non_paranoid_models, dependent: :destroy
293
401
  end
294
402
 
295
403
  class RelatedModel < ActiveRecord::Base
@@ -314,3 +422,24 @@ class Job < ActiveRecord::Base
314
422
  belongs_to :employer
315
423
  belongs_to :employee
316
424
  end
425
+
426
+ class CustomColumnModel < ActiveRecord::Base
427
+ acts_as_paranoid column: :destroyed_at
428
+ end
429
+
430
+ class NonParanoidModel < ActiveRecord::Base
431
+ end
432
+
433
+ class ParanoidModelWithObservers < ParanoidModel
434
+ def observers_notified
435
+ @observers_notified ||= []
436
+ end
437
+
438
+ def self.notify_observer(*args)
439
+ observers_notified << args
440
+ end
441
+ end
442
+
443
+ class ParanoidModelWithoutObservers < ParanoidModel
444
+ self.class.send(remove_method :notify_observers) if method_defined?(:notify_observers)
445
+ end
metadata CHANGED
@@ -1,69 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paranoia
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - radarlistener@gmail.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-24 00:00:00.000000000 Z
11
+ date: 2014-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '3.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.0.0
41
- - !ruby/object:Gem::Dependency
42
- name: sqlite3
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '>='
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - '>='
45
+ - - ">="
60
46
  - !ruby/object:Gem::Version
61
47
  version: '0'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - '>='
52
+ - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  description: Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using
@@ -77,8 +63,8 @@ executables: []
77
63
  extensions: []
78
64
  extra_rdoc_files: []
79
65
  files:
80
- - .gitignore
81
- - .travis.yml
66
+ - ".gitignore"
67
+ - ".travis.yml"
82
68
  - Gemfile
83
69
  - LICENSE
84
70
  - README.md
@@ -96,17 +82,17 @@ require_paths:
96
82
  - lib
97
83
  required_ruby_version: !ruby/object:Gem::Requirement
98
84
  requirements:
99
- - - '>='
85
+ - - ">="
100
86
  - !ruby/object:Gem::Version
101
87
  version: '0'
102
88
  required_rubygems_version: !ruby/object:Gem::Requirement
103
89
  requirements:
104
- - - '>='
90
+ - - ">="
105
91
  - !ruby/object:Gem::Version
106
92
  version: 1.3.6
107
93
  requirements: []
108
94
  rubyforge_project: paranoia
109
- rubygems_version: 2.1.0
95
+ rubygems_version: 2.2.0
110
96
  signing_key:
111
97
  specification_version: 4
112
98
  summary: Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much,