paranoia 1.3.2 → 1.3.3

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