acts_as_paranoid 0.6.0 → 0.6.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
  SHA256:
3
- metadata.gz: 45239a76ba68990d6273016fdba8c582ecd44d93475ebabf5ae6717b5c58ddcc
4
- data.tar.gz: 330ed8379df4137f3016de1db1def095ae268b3f99a6dce075b4fea8adf4eb42
3
+ metadata.gz: 5e2b91eb0df1ed0d8cfb306f7272860c3f03e1da75259b202d222c1fece04927
4
+ data.tar.gz: 6dc0038fd41642f40e774e32eebe18ee5da6eae6750077781902aed17975ad1e
5
5
  SHA512:
6
- metadata.gz: a2f9c1437125d81959809addcdbee54003711ad8f339b2d1f78987b81aaab3766ac813563c110879f594b127ca90d59186ba84ca0079eeec5e69005b9e0dae08
7
- data.tar.gz: 51f5f32565a0ae3f90de5f7afe517372a55c63c887c9de7ed07791f196b03e568f9cc275586c43f9e19806c5f45ef1431de4f446c819344cfebda64c0608d997
6
+ metadata.gz: 1587bb00c56acbb5daf5929ef4588f89622eb83141258c2f686ce5ed55719849fa17064bc011cf6cd3720d05f7900f1121442ba6abb71cdcc977f637800b00dc
7
+ data.tar.gz: 026be4a7b7eec225d6a1113af33aa610438bc4f895d8539e1bf6159f8bfe07bda3add7733ecdec982ac85a4b28f1e3abc9a4cd85ab56f07bc6f9fcb4bd20211b
data/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # CHANGELOG
2
+
3
+ Notable changes to this project will be documented in this file.
4
+
5
+ ## 0.6.3
6
+
7
+ * Update Travis CI configuration ([#137], by [Matijs van Zuijlen][mvz]).
8
+ * Add predicate to check if record was soft deleted or hard deleted ([#136],
9
+ by [Aymeric Le Dorze][aymeric-ledorze]).
10
+ * Add support for recover! method ([#75], by [vinoth][avinoth]).
11
+ * Fix a record being dirty after destroying it ([#135], by
12
+ [Aymeric Le Dorze][aymeric-ledorze]).
13
+
14
+ ## 0.6.2
15
+
16
+ * Prevent recovery of non-deleted records
17
+ ([#133], by [Mary Beliveau][marycodes2] and [Valerie Woolard][valeriecodes])
18
+ * Allow model to set `table_name` after `acts_as_paranoid` macro
19
+ ([#131], by [Alex Wheeler][AlexWheeler]).
20
+ * Make counter cache work with a custom column name and with optional
21
+ associations ([#123], by [Ned Campion][nedcampion]).
22
+
23
+ ## 0.6.1
24
+
25
+ * Add support for Rails 6 ([#124], by [Daniel Rice][danielricecodes],
26
+ [Josh Bryant][jbryant92], and [Romain Alexandre][RomainAlexandre])
27
+ * Add support for incrementing and decrementing counter cache columns on
28
+ associated objects ([#119], by [Dimitar Lukanov][shadydealer])
29
+ * Add `:double_tap_destroys_fully` option, with default `true` ([#116],
30
+ by [Michael Riviera][ri4a]).
31
+ * Officially support Ruby 2.6 ([#114], by [Matijs van Zuijlen][mvz]).
32
+
33
+ ## 0.6.0 and earlier
34
+
35
+ (To be added)
36
+
37
+ <!-- Contributors -->
38
+
39
+ [ri4a]: https://github.com/ri4a
40
+ [mvz]: https://github.com/mvz
41
+ [shadydealer]: https://github.com/shadydealer
42
+ [danielricecodes]: https://github.com/danielricecodes
43
+ [jbryant92]: https://github.com/jbryant92
44
+ [nedcampion]: https://github.com/nedcampion
45
+ [RomainAlexandre]: https://github.com/RomainAlexandre
46
+ [AlexWheeler]: https://github.com/AlexWheeler
47
+ [marycodes2]: https://github.com/marycodes2
48
+ [valeriecodes]: https://github.com/valeriecodes
49
+ [aymeric-ledorze]: https://github.com/aymeric-ledorze
50
+ [avinoth]: https://github.com/avinoth
51
+
52
+ <!-- issues & pull requests -->
53
+
54
+ [#137]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/137
55
+ [#136]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/136
56
+ [#135]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/135
57
+ [#133]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/133
58
+ [#131]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/131
59
+ [#124]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/124
60
+ [#123]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/123
61
+ [#119]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/119
62
+ [#116]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/116
63
+ [#114]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/114
64
+ [#75]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/75
data/README.md CHANGED
@@ -9,12 +9,12 @@ recoverable later.
9
9
 
10
10
  ## Support
11
11
 
12
- **This branch targets Rails 4.2, 5.0 and 5.1, with experimental support for 5.2**
12
+ **This branch targets Rails 4.2, 5.0 and 5.1, with experimental support for 5.2+**
13
13
 
14
- If you're working with another version, switch to the corresponding branch, or
14
+ If you're working with Rails 4.1-, switch to the corresponding branch, or
15
15
  require an older version of the `acts_as_paranoid` gem.
16
16
 
17
- ### Known issues with Rails 5.2
17
+ ### Known issues with Rails 5.2+
18
18
 
19
19
  * Using acts_as_paranoid and ActiveStorage on the same model
20
20
  [leads to a SystemStackError](https://github.com/ActsAsParanoid/acts_as_paranoid/issues/103).
@@ -114,6 +114,10 @@ p.destroy # does NOT delete the first record, just hides it
114
114
  Paranoiac.only_deleted.where(:id => p.id).first.destroy # deletes the first record from the database
115
115
  ```
116
116
 
117
+ This behaviour can be disabled by setting the configuration option. In a future version, `false` will be the default setting.
118
+
119
+ - `:double_tap_destroys_fully => false`
120
+
117
121
  ### Recovery
118
122
 
119
123
  Recovery is easy. Just invoke `recover` on it, like this:
@@ -167,6 +171,22 @@ or in the recover statement
167
171
  Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recovery_window => 30.seconds)
168
172
  ```
169
173
 
174
+ ### recover!
175
+
176
+ You can invoke `recover!` if you wish to raise an error if the recovery fails. The error generally stems from ActiveRecord.
177
+
178
+ ```
179
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover!
180
+
181
+ ### => ActiveRecord::RecordInvalid: Validation failed: Name already exists
182
+ ```
183
+
184
+ Optionally, you may also raise the error by passing `:raise_error => true` to the `recover` method. This behaves the same as `recover!`.
185
+
186
+ ```
187
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:raise_error => true)
188
+ ```
189
+
170
190
  ### Validation
171
191
  ActiveRecord's built-in uniqueness validation does not account for records deleted by ActsAsParanoid. If you want to check for uniqueness among non-deleted records only, use the macro `validates_as_paranoid` in your model. Then, instead of using `validates_uniqueness_of`, use `validates_uniqueness_of_without_deleted`. This will keep deleted records from counting against the uniqueness check.
172
192
 
@@ -259,11 +279,26 @@ child.parent #=> nil
259
279
  child.parent_including_deleted #=> Parent (it works!)
260
280
  ```
261
281
 
282
+ ### Callbacks
283
+
284
+ There are couple of callbacks that you may use when dealing with deletion and recovery of objects. There is `before_recover` and `after_recover` which will be
285
+ triggered before and after the recovery of an object respectively.
286
+
287
+ Default ActiveRecord callbaks such as `before_destroy` and `after_destroy` will be triggered around `.destroy!` and `.destroy_fully!`.
288
+
289
+ ```
290
+ class Paranoiac < ActiveRecord::Base
291
+ acts_as_paranoid
292
+
293
+ before_recover :set_counts
294
+ after_recover :update_logs
295
+ end
296
+ ```
297
+
262
298
  ## Caveats
263
299
 
264
300
  Watch out for these caveats:
265
301
 
266
-
267
302
  - You cannot use scopes named `with_deleted` and `only_deleted`
268
303
  - You cannot use scopes named `deleted_inside_time_window`, `deleted_before_time`, `deleted_after_time` **if** your paranoid column's type is `time`
269
304
  - You cannot name association `*_with_deleted`
@@ -283,5 +318,6 @@ Watch out for these caveats:
283
318
  * To [Jean Boussier](https://github.com/byroot) for initial Rails 4.0.0 support
284
319
  * To [Matijs van Zuijlen](https://github.com/mvz) for Rails 4.1 and 4.2 support
285
320
  * To [Andrey Ponomarenko](https://github.com/sjke) for Rails 5 support
321
+ * To [Daniel Rice](https://github.com/danielricecodes), [Josh Bryant](https://github.com/jbryant92), and [Romain Alexandre](https://github.com/RomainAlexandre) for Rails 6.0 support.
286
322
 
287
323
  See `LICENSE`.
@@ -17,16 +17,18 @@ module ActsAsParanoid
17
17
  def acts_as_paranoid(options = {})
18
18
  raise ArgumentError, "Hash expected, got #{options.class.name}" if not options.is_a?(Hash) and not options.empty?
19
19
 
20
- class_attribute :paranoid_configuration, :paranoid_column_reference
20
+ class_attribute :paranoid_configuration
21
21
 
22
- self.paranoid_configuration = { :column => "deleted_at", :column_type => "time", :recover_dependent_associations => true, :dependent_recovery_window => 2.minutes, :recovery_value => nil }
22
+ self.paranoid_configuration = { :column => "deleted_at", :column_type => "time", :recover_dependent_associations => true, :dependent_recovery_window => 2.minutes, :recovery_value => nil, double_tap_destroys_fully: true }
23
23
  self.paranoid_configuration.merge!({ :deleted_value => "deleted" }) if options[:column_type] == "string"
24
24
  self.paranoid_configuration.merge!({ :allow_nulls => true }) if options[:column_type] == "boolean"
25
25
  self.paranoid_configuration.merge!(options) # user options
26
26
 
27
27
  raise ArgumentError, "'time', 'boolean' or 'string' expected for :column_type option, got #{paranoid_configuration[:column_type]}" unless ['time', 'boolean', 'string'].include? paranoid_configuration[:column_type]
28
28
 
29
- self.paranoid_column_reference = "#{self.table_name}.#{paranoid_configuration[:column]}"
29
+ def self.paranoid_column_reference
30
+ "#{self.table_name}.#{paranoid_configuration[:column]}"
31
+ end
30
32
 
31
33
  return if paranoid?
32
34
 
@@ -31,7 +31,7 @@ module ActsAsParanoid
31
31
  association = association(:#{target})
32
32
  return nil if association.options[:polymorphic] && association.klass.nil?
33
33
  return #{target}_without_unscoped(*args) unless association.klass.paranoid?
34
- association.klass.with_deleted.scoping { #{target}_without_unscoped(*args) }
34
+ association.klass.with_deleted.scoping { association.klass.unscoped { #{target}_without_unscoped(*args) } }
35
35
  end
36
36
  alias_method :#{target}_without_unscoped, :#{target}
37
37
  alias_method :#{target}, :#{target}_with_unscoped
@@ -108,7 +108,7 @@ module ActsAsParanoid
108
108
  # Straight from ActiveRecord 5.1!
109
109
  def delete
110
110
  self.class.delete(id) if persisted?
111
- @destroyed = true
111
+ stale_paranoid_value
112
112
  freeze
113
113
  end
114
114
 
@@ -116,9 +116,16 @@ module ActsAsParanoid
116
116
  with_transaction_returning_status do
117
117
  run_callbacks :destroy do
118
118
  destroy_dependent_associations!
119
- # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
120
- self.class.delete_all!(Hash[[Array(self.class.primary_key), Array(self.id)].transpose]) if persisted?
121
- self.paranoid_value = self.class.delete_now_value
119
+
120
+ if persisted?
121
+ # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
122
+ self.class.delete_all!(Hash[[Array(self.class.primary_key), Array(self.id)].transpose])
123
+
124
+ decrement_counters_on_associations
125
+ end
126
+
127
+ stale_paranoid_value
128
+ @destroyed = true
122
129
  freeze
123
130
  end
124
131
  end
@@ -128,40 +135,57 @@ module ActsAsParanoid
128
135
  if !deleted?
129
136
  with_transaction_returning_status do
130
137
  run_callbacks :destroy do
131
- # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
132
- @_trigger_destroy_callback = if persisted?
133
- self.class.delete_all(Hash[[Array(self.class.primary_key), Array(self.id)].transpose])
134
- else
135
- true
136
- end
137
138
 
138
- self.paranoid_value = self.class.delete_now_value
139
+ if persisted?
140
+ # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
141
+ self.class.delete_all(Hash[[Array(self.class.primary_key), Array(self.id)].transpose])
142
+
143
+ decrement_counters_on_associations
144
+ end
145
+
146
+ @_trigger_destroy_callback = true
147
+
148
+ stale_paranoid_value
139
149
  self
140
150
  end
141
151
  end
142
152
  else
143
- destroy_fully!
153
+ if paranoid_configuration[:double_tap_destroys_fully]
154
+ destroy_fully!
155
+ end
144
156
  end
145
157
  end
146
158
 
147
159
  alias_method :destroy, :destroy!
148
160
 
149
161
  def recover(options={})
162
+ return if !self.deleted?
150
163
  options = {
151
164
  :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
152
- :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window]
165
+ :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window],
166
+ :raise_error => false
153
167
  }.merge(options)
154
168
 
155
169
  self.class.transaction do
156
170
  run_callbacks :recover do
157
171
  recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
158
-
172
+ increment_counters_on_associations
159
173
  self.paranoid_value = self.class.paranoid_configuration[:recovery_value]
160
- self.save
174
+ if options[:raise_error]
175
+ self.save!
176
+ else
177
+ self.save
178
+ end
161
179
  end
162
180
  end
163
181
  end
164
182
 
183
+ def recover!(options={})
184
+ options[:raise_error] = true
185
+
186
+ recover(options)
187
+ end
188
+
165
189
  def recover_dependent_associations(window, options)
166
190
  self.class.dependent_associations.each do |reflection|
167
191
  next unless (klass = get_reflection_class(reflection)).paranoid?
@@ -169,7 +193,11 @@ module ActsAsParanoid
169
193
  scope = klass.only_deleted
170
194
 
171
195
  # Merge in the association's scope
172
- scope = scope.merge(association(reflection.name).association_scope)
196
+ scope = if ActiveRecord::VERSION::MAJOR >= 6
197
+ scope.merge(ActiveRecord::Associations::AssociationScope.scope(association(reflection.name)))
198
+ else
199
+ scope.merge(association(reflection.name).association_scope)
200
+ end
173
201
 
174
202
  # We can only recover by window if both parent and dependant have a
175
203
  # paranoid column type of :time.
@@ -190,7 +218,11 @@ module ActsAsParanoid
190
218
  scope = klass.only_deleted
191
219
 
192
220
  # Merge in the association's scope
193
- scope = scope.merge(association(reflection.name).association_scope)
221
+ scope = if ActiveRecord::VERSION::MAJOR >= 6
222
+ scope.merge(ActiveRecord::Associations::AssociationScope.scope(association(reflection.name)))
223
+ else
224
+ scope.merge(association(reflection.name).association_scope)
225
+ end
194
226
 
195
227
  scope.each do |object|
196
228
  object.destroy!
@@ -199,7 +231,7 @@ module ActsAsParanoid
199
231
  end
200
232
 
201
233
  def deleted?
202
- !if self.class.string_type_with_deleted_value?
234
+ @destroyed || !if self.class.string_type_with_deleted_value?
203
235
  paranoid_value != self.class.delete_now_value || paranoid_value.nil?
204
236
  elsif self.class.boolean_type_not_nullable?
205
237
  paranoid_value == false
@@ -210,6 +242,12 @@ module ActsAsParanoid
210
242
 
211
243
  alias_method :destroyed?, :deleted?
212
244
 
245
+ def deleted_fully?
246
+ @destroyed
247
+ end
248
+
249
+ alias_method :destroyed_fully?, :deleted_fully?
250
+
213
251
  private
214
252
 
215
253
  def get_reflection_class(reflection)
@@ -223,5 +261,36 @@ module ActsAsParanoid
223
261
  def paranoid_value=(value)
224
262
  self.send("#{self.class.paranoid_column}=", value)
225
263
  end
264
+
265
+ def update_counters_on_associations method_sym
266
+
267
+ return unless [:decrement_counter, :increment_counter].include? method_sym
268
+
269
+ each_counter_cached_association_reflection do |assoc_reflection|
270
+ if associated_object = send(assoc_reflection.name)
271
+ counter_cache_column = assoc_reflection.counter_cache_column
272
+ associated_object.class.send(method_sym, counter_cache_column, associated_object.id)
273
+ end
274
+ end
275
+ end
276
+
277
+ def each_counter_cached_association_reflection
278
+ _reflections.each do |name, reflection|
279
+ yield reflection if reflection.belongs_to? && reflection.counter_cache_column
280
+ end
281
+ end
282
+
283
+ def increment_counters_on_associations
284
+ update_counters_on_associations :increment_counter
285
+ end
286
+
287
+ def decrement_counters_on_associations
288
+ update_counters_on_associations :decrement_counter
289
+ end
290
+
291
+ def stale_paranoid_value
292
+ self.paranoid_value = self.class.delete_now_value
293
+ clear_attribute_changes([self.class.paranoid_column])
294
+ end
226
295
  end
227
296
  end
@@ -8,10 +8,9 @@ module ActsAsParanoid
8
8
 
9
9
  class UniquenessWithoutDeletedValidator
10
10
  def self.[](version)
11
- version = version.to_s
12
- name = "V#{version.tr('.', '_')}"
11
+ name = "V#{version.to_s.tr('.', '_')}"
13
12
  unless constants.include? name.to_sym
14
- raise "Unknown validator version #{version.inspect}; expected one of #{constants.sort.join(', ')}"
13
+ raise "Unknown validator version #{name.inspect}; expected one of #{constants.sort.join(', ')}"
15
14
  end
16
15
  const_get name
17
16
  end
@@ -38,8 +37,11 @@ module ActsAsParanoid
38
37
  protected
39
38
 
40
39
  def build_relation(klass, attribute, value)
41
- return super(klass, klass.arel_table, attribute, value) if ActiveRecord::VERSION::MINOR == 0
42
- super
40
+ if ActiveRecord::VERSION::MINOR == 0 && ActiveRecord::VERSION::MAJOR == 5
41
+ return super(klass, klass.arel_table, attribute, value)
42
+ else
43
+ super
44
+ end
43
45
  end
44
46
  end
45
47
 
@@ -75,6 +77,9 @@ module ActsAsParanoid
75
77
  end
76
78
  end
77
79
  end
80
+
81
+ class V6 < V5
82
+ end
78
83
  end
79
84
 
80
85
  module ClassMethods
@@ -1,3 +1,3 @@
1
1
  module ActsAsParanoid
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.3"
3
3
  end
@@ -41,6 +41,48 @@ class AssociationsTest < ParanoidBaseTest
41
41
  includes_values = ParanoidTime.includes(:not_paranoid).includes_values
42
42
 
43
43
  assert_equal includes_values, paranoid_has_many_dependant.association(:paranoid_time_with_scope).scope.includes_values
44
+
45
+ paranoid_time = ParanoidTime.create(name: 'not-hello')
46
+ paranoid_has_many_dependant.paranoid_time = paranoid_time
47
+ paranoid_has_many_dependant.save!
48
+
49
+ assert_nil paranoid_has_many_dependant.paranoid_time_with_scope
50
+
51
+ paranoid_time.update(name: 'hello')
52
+
53
+ paranoid_has_many_dependant.reload
54
+
55
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_scope
56
+
57
+ paranoid_time.destroy
58
+
59
+ paranoid_has_many_dependant.reload
60
+
61
+ assert_nil paranoid_has_many_dependant.paranoid_time_with_scope
62
+ end
63
+
64
+ def test_belongs_to_with_scope_and_deleted_option
65
+ paranoid_has_many_dependant = ParanoidHasManyDependant.new
66
+ includes_values = ParanoidTime.includes(:not_paranoid).includes_values
67
+
68
+ assert_equal includes_values, paranoid_has_many_dependant
69
+ .association(:paranoid_time_with_scope_with_deleted).scope.includes_values
70
+
71
+ paranoid_time = ParanoidTime.create(name: 'not-hello')
72
+ paranoid_has_many_dependant.paranoid_time = paranoid_time
73
+ paranoid_has_many_dependant.save!
74
+
75
+ assert_nil paranoid_has_many_dependant.paranoid_time_with_scope_with_deleted
76
+
77
+ paranoid_time.update(name: 'hello')
78
+ paranoid_has_many_dependant.reload
79
+
80
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_scope_with_deleted
81
+
82
+ paranoid_time.destroy
83
+ paranoid_has_many_dependant.reload
84
+
85
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_scope_with_deleted
44
86
  end
45
87
 
46
88
  def test_belongs_to_with_deleted
@@ -51,9 +93,10 @@ class AssociationsTest < ParanoidBaseTest
51
93
  assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_deleted
52
94
 
53
95
  paranoid_time.destroy
96
+ paranoid_has_many_dependant.reload
54
97
 
55
- assert_nil paranoid_has_many_dependant.reload.paranoid_time
56
- assert_equal paranoid_time, paranoid_has_many_dependant.reload.paranoid_time_with_deleted
98
+ assert_nil paranoid_has_many_dependant.paranoid_time
99
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_deleted
57
100
  end
58
101
 
59
102
  def test_belongs_to_polymorphic_with_deleted
@@ -228,7 +271,7 @@ class AssociationsTest < ParanoidBaseTest
228
271
  end
229
272
 
230
273
  def test_mass_assignment_of_paranoid_column_enabled
231
- if ActiveRecord::VERSION::MAJOR > 4 && ActiveRecord::VERSION::MINOR > 1
274
+ if Gem.loaded_specs['activerecord'].version >= Gem::Version.new('5.2.0')
232
275
  skip 'Creation as deleted is not supported with Rails >= 5.2'
233
276
  end
234
277
  now = Time.now
data/test/test_core.rb CHANGED
@@ -69,6 +69,13 @@ class ParanoidTest < ParanoidBaseTest
69
69
  assert_not_nil pt.paranoid_value
70
70
  end
71
71
 
72
+ def test_non_persisted_delete
73
+ pt = ParanoidTime.new
74
+ assert_nil pt.paranoid_value
75
+ pt.delete
76
+ assert_not_nil pt.paranoid_value
77
+ end
78
+
72
79
  def test_non_persisted_destroy!
73
80
  pt = ParanoidTime.new
74
81
  assert_nil pt.paranoid_value
@@ -94,6 +101,15 @@ class ParanoidTest < ParanoidBaseTest
94
101
  assert_equal 1, ParanoidString.count
95
102
  end
96
103
 
104
+ def test_recovery!
105
+ ParanoidBoolean.first.destroy
106
+ ParanoidBoolean.create(:name => 'paranoid')
107
+
108
+ assert_raise do
109
+ ParanoidBoolean.only_deleted.first.recover!
110
+ end
111
+ end
112
+
97
113
  def setup_recursive_tests
98
114
  @paranoid_time_object = ParanoidTime.first
99
115
 
@@ -265,6 +281,24 @@ class ParanoidTest < ParanoidBaseTest
265
281
  assert_equal 0, HasOneNotParanoid.count
266
282
  end
267
283
 
284
+ def test_dirty
285
+ pt = ParanoidTime.create
286
+ pt.destroy
287
+ assert_not pt.changed?
288
+ end
289
+
290
+ def test_delete_dirty
291
+ pt = ParanoidTime.create
292
+ pt.delete
293
+ assert_not pt.changed?
294
+ end
295
+
296
+ def test_destroy_fully_dirty
297
+ pt = ParanoidTime.create
298
+ pt.destroy_fully!
299
+ assert_not pt.changed?
300
+ end
301
+
268
302
  def test_deleted?
269
303
  ParanoidTime.first.destroy
270
304
  assert ParanoidTime.with_deleted.first.deleted?
@@ -273,6 +307,43 @@ class ParanoidTest < ParanoidBaseTest
273
307
  assert ParanoidString.with_deleted.first.deleted?
274
308
  end
275
309
 
310
+ def test_delete_deleted?
311
+ ParanoidTime.first.delete
312
+ assert ParanoidTime.with_deleted.first.deleted?
313
+
314
+ ParanoidString.first.delete
315
+ assert ParanoidString.with_deleted.first.deleted?
316
+ end
317
+
318
+ def test_destroy_fully_deleted?
319
+ object = ParanoidTime.first
320
+ object.destroy_fully!
321
+ assert object.deleted?
322
+
323
+ object = ParanoidString.first
324
+ object.destroy_fully!
325
+ assert object.deleted?
326
+ end
327
+
328
+ def test_deleted_fully?
329
+ ParanoidTime.first.destroy
330
+ assert_not ParanoidTime.with_deleted.first.deleted_fully?
331
+
332
+ ParanoidString.first.destroy
333
+ assert ParanoidString.with_deleted.first.deleted?
334
+ end
335
+
336
+ def test_delete_deleted_fully?
337
+ ParanoidTime.first.delete
338
+ assert_not ParanoidTime.with_deleted.first.deleted_fully?
339
+ end
340
+
341
+ def test_destroy_fully_deleted_fully?
342
+ object = ParanoidTime.first
343
+ object.destroy_fully!
344
+ assert object.deleted_fully?
345
+ end
346
+
276
347
  def test_paranoid_destroy_callbacks
277
348
  @paranoid_with_callback = ParanoidWithCallback.first
278
349
  ParanoidWithCallback.transaction do
@@ -312,6 +383,14 @@ class ParanoidTest < ParanoidBaseTest
312
383
  assert @paranoid_with_callback.called_after_recover
313
384
  end
314
385
 
386
+ def test_recovery_callbacks_without_destroy
387
+ @paranoid_with_callback = ParanoidWithCallback.first
388
+ @paranoid_with_callback.recover
389
+
390
+ assert_nil @paranoid_with_callback.called_before_recover
391
+ assert_nil @paranoid_with_callback.called_after_recover
392
+ end
393
+
315
394
  def test_delete_by_multiple_id_is_paranoid
316
395
  model_a = ParanoidBelongsDependant.create
317
396
  model_b = ParanoidBelongsDependant.create
@@ -437,4 +516,94 @@ class ParanoidTest < ParanoidBaseTest
437
516
  2.times { ps.destroy }
438
517
  assert_equal 0, ParanoidBooleanNotNullable.with_deleted.where(:id => ps).count
439
518
  end
519
+
520
+ def test_no_double_tap_destroys_fully
521
+ ps = ParanoidNoDoubleTapDestroysFully.create!()
522
+ 2.times { ps.destroy }
523
+ assert_equal 1, ParanoidNoDoubleTapDestroysFully.with_deleted.where(:id => ps).count
524
+ end
525
+
526
+ def test_decrement_counters
527
+ paranoid_boolean = ParanoidBoolean.create!()
528
+ paranoid_with_counter_cache = ParanoidWithCounterCache.create!(paranoid_boolean: paranoid_boolean)
529
+
530
+ assert_equal 1, paranoid_boolean.paranoid_with_counter_caches_count
531
+
532
+ paranoid_with_counter_cache.destroy
533
+
534
+ assert_equal 0, paranoid_boolean.reload.paranoid_with_counter_caches_count
535
+ end
536
+
537
+ def test_decrement_custom_counters
538
+ paranoid_boolean = ParanoidBoolean.create!()
539
+ paranoid_with_custom_counter_cache = ParanoidWithCustomCounterCache.create!(paranoid_boolean: paranoid_boolean)
540
+
541
+ assert_equal 1, paranoid_boolean.custom_counter_cache
542
+
543
+ paranoid_with_custom_counter_cache.destroy
544
+
545
+ assert_equal 0, paranoid_boolean.reload.custom_counter_cache
546
+ end
547
+
548
+ def test_destroy_with_optional_belongs_to_and_counter_cache
549
+ ps = ParanoidWithCounterCacheOnOptionalBelognsTo.create!()
550
+ ps.destroy
551
+ assert_equal 1, ParanoidWithCounterCacheOnOptionalBelognsTo.only_deleted.where(:id => ps).count
552
+ end
553
+
554
+ def test_hard_destroy_decrement_counters
555
+ paranoid_boolean = ParanoidBoolean.create!()
556
+ paranoid_with_counter_cache = ParanoidWithCounterCache.create!(paranoid_boolean: paranoid_boolean)
557
+
558
+ assert_equal 1, paranoid_boolean.paranoid_with_counter_caches_count
559
+
560
+ paranoid_with_counter_cache.destroy_fully!
561
+
562
+ assert_equal 0, paranoid_boolean.reload.paranoid_with_counter_caches_count
563
+ end
564
+
565
+ def test_hard_destroy_decrement_custom_counters
566
+ paranoid_boolean = ParanoidBoolean.create!()
567
+ paranoid_with_custom_counter_cache = ParanoidWithCustomCounterCache.create!(paranoid_boolean: paranoid_boolean)
568
+
569
+ assert_equal 1, paranoid_boolean.custom_counter_cache
570
+
571
+ paranoid_with_custom_counter_cache.destroy_fully!
572
+
573
+ assert_equal 0, paranoid_boolean.reload.custom_counter_cache
574
+ end
575
+
576
+ def test_increment_counters
577
+ paranoid_boolean = ParanoidBoolean.create!()
578
+ paranoid_with_counter_cache = ParanoidWithCounterCache.create!(paranoid_boolean: paranoid_boolean)
579
+
580
+ assert_equal 1, paranoid_boolean.paranoid_with_counter_caches_count
581
+
582
+ paranoid_with_counter_cache.destroy
583
+
584
+ assert_equal 0, paranoid_boolean.reload.paranoid_with_counter_caches_count
585
+
586
+ paranoid_with_counter_cache.recover
587
+
588
+ assert_equal 1, paranoid_boolean.reload.paranoid_with_counter_caches_count
589
+ end
590
+
591
+ def test_increment_custom_counters
592
+ paranoid_boolean = ParanoidBoolean.create!()
593
+ paranoid_with_custom_counter_cache = ParanoidWithCustomCounterCache.create!(paranoid_boolean: paranoid_boolean)
594
+
595
+ assert_equal 1, paranoid_boolean.custom_counter_cache
596
+
597
+ paranoid_with_custom_counter_cache.destroy
598
+
599
+ assert_equal 0, paranoid_boolean.reload.custom_counter_cache
600
+
601
+ paranoid_with_custom_counter_cache.recover
602
+
603
+ assert_equal 1, paranoid_boolean.reload.custom_counter_cache
604
+ end
605
+
606
+ def test_explicitly_setting_table_name_after_acts_as_paranoid_macro
607
+ assert_equal "explicit_table.deleted_at", ParanoidWithExplicitTableNameAfterMacro.paranoid_column_reference
608
+ end
440
609
  end
@@ -4,7 +4,6 @@ class MultipleDefaultScopesTest < ParanoidBaseTest
4
4
  def setup
5
5
  setup_db
6
6
 
7
- # Naturally, the default scope for humans is male. Sexism++
8
7
  ParanoidPolygon.create! :sides => 3
9
8
  ParanoidPolygon.create! :sides => 3
10
9
  ParanoidPolygon.create! :sides => 3
data/test/test_helper.rb CHANGED
@@ -31,7 +31,8 @@ def setup_db
31
31
  t.string :name
32
32
  t.boolean :is_deleted
33
33
  t.integer :paranoid_time_id
34
-
34
+ t.integer :paranoid_with_counter_caches_count
35
+ t.integer :custom_counter_cache
35
36
  timestamps t
36
37
  end
37
38
 
@@ -148,7 +149,7 @@ def setup_db
148
149
  timestamps t
149
150
  end
150
151
 
151
- create_table :paranoid_forests do |t|
152
+ create_table :paranoid_forests do |t|
152
153
  t.string :name
153
154
  t.boolean :rainforest
154
155
  t.datetime :deleted_at
@@ -208,6 +209,18 @@ def setup_db
208
209
 
209
210
  timestamps t
210
211
  end
212
+
213
+ create_table :paranoid_no_double_tap_destroys_fullies do |t|
214
+ t.datetime :deleted_at
215
+ end
216
+
217
+ create_table :paranoid_with_counter_caches do |t|
218
+ t.string :name
219
+ t.datetime :deleted_at
220
+ t.integer :paranoid_boolean_id
221
+
222
+ timestamps t
223
+ end
211
224
  end
212
225
  end
213
226
 
@@ -247,6 +260,8 @@ class ParanoidBoolean < ActiveRecord::Base
247
260
 
248
261
  belongs_to :paranoid_time
249
262
  has_one :paranoid_has_one_dependant, :dependent => :destroy
263
+ has_many :paranoid_with_counter_cache, :dependent => :destroy
264
+ has_many :paranoid_with_custom_counter_cache, :dependent => :destroy
250
265
  end
251
266
 
252
267
  class ParanoidString < ActiveRecord::Base
@@ -256,6 +271,10 @@ end
256
271
  class NotParanoid < ActiveRecord::Base
257
272
  end
258
273
 
274
+ class ParanoidNoDoubleTapDestroysFully < ActiveRecord::Base
275
+ acts_as_paranoid :double_tap_destroys_fully => false
276
+ end
277
+
259
278
  class HasOneNotParanoid < ActiveRecord::Base
260
279
  belongs_to :paranoid_time, :with_deleted => true
261
280
  end
@@ -265,11 +284,35 @@ class DoubleHasOneNotParanoid < HasOneNotParanoid
265
284
  belongs_to :paranoid_time, :with_deleted => true
266
285
  end
267
286
 
287
+ class ParanoidWithCounterCache < ActiveRecord::Base
288
+ acts_as_paranoid
289
+ belongs_to :paranoid_boolean, :counter_cache => true
290
+ end
291
+
292
+ class ParanoidWithCustomCounterCache < ActiveRecord::Base
293
+ self.table_name = "paranoid_with_counter_caches"
294
+
295
+ acts_as_paranoid
296
+ belongs_to :paranoid_boolean, counter_cache: :custom_counter_cache
297
+ end
298
+
299
+ class ParanoidWithCounterCacheOnOptionalBelognsTo < ActiveRecord::Base
300
+ self.table_name = "paranoid_with_counter_caches"
301
+
302
+ acts_as_paranoid
303
+ if ActiveRecord::VERSION::MAJOR < 5
304
+ belongs_to :paranoid_boolean, counter_cache: true, required: false
305
+ else
306
+ belongs_to :paranoid_boolean, counter_cache: true, optional: true
307
+ end
308
+ end
309
+
268
310
  class ParanoidHasManyDependant < ActiveRecord::Base
269
311
  acts_as_paranoid
270
312
  belongs_to :paranoid_time
271
- belongs_to :paranoid_time_with_scope, -> { includes(:not_paranoid) }, :class_name => 'ParanoidTime', :foreign_key => :paranoid_time_id
313
+ belongs_to :paranoid_time_with_scope, -> { where(name: 'hello').includes(:not_paranoid) }, :class_name => 'ParanoidTime', :foreign_key => :paranoid_time_id
272
314
  belongs_to :paranoid_time_with_deleted, :class_name => 'ParanoidTime', :foreign_key => :paranoid_time_id, :with_deleted => true
315
+ belongs_to :paranoid_time_with_scope_with_deleted, -> { where(name: 'hello').includes(:not_paranoid) }, :class_name => 'ParanoidTime', :foreign_key => :paranoid_time_id, :with_deleted => true
273
316
  belongs_to :paranoid_time_polymorphic_with_deleted, :class_name => 'ParanoidTime', :foreign_key => :paranoid_time_id, :polymorphic => true, :with_deleted => true
274
317
 
275
318
  belongs_to :paranoid_belongs_dependant, :dependent => :destroy
@@ -470,3 +513,7 @@ class ParanoidBooleanNotNullable < ActiveRecord::Base
470
513
  acts_as_paranoid column: 'deleted', column_type: 'boolean', allow_nulls: false
471
514
  end
472
515
 
516
+ class ParanoidWithExplicitTableNameAfterMacro < ActiveRecord::Base
517
+ acts_as_paranoid
518
+ self.table_name = "explicit_table"
519
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_paranoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zachary Scott
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-06-07 00:00:00.000000000 Z
13
+ date: 2020-01-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '4.2'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.0'
24
+ version: '7.0'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +31,7 @@ dependencies:
31
31
  version: '4.2'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.0'
34
+ version: '7.0'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activesupport
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -41,7 +41,7 @@ dependencies:
41
41
  version: '4.2'
42
42
  - - "<"
43
43
  - !ruby/object:Gem::Version
44
- version: '6.0'
44
+ version: '7.0'
45
45
  type: :runtime
46
46
  prerelease: false
47
47
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,21 +51,27 @@ dependencies:
51
51
  version: '4.2'
52
52
  - - "<"
53
53
  - !ruby/object:Gem::Version
54
- version: '6.0'
54
+ version: '7.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '1.5'
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '3.0'
62
65
  type: :development
63
66
  prerelease: false
64
67
  version_requirements: !ruby/object:Gem::Requirement
65
68
  requirements:
66
- - - "~>"
69
+ - - ">="
67
70
  - !ruby/object:Gem::Version
68
71
  version: '1.5'
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
69
75
  - !ruby/object:Gem::Dependency
70
76
  name: rake
71
77
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +120,20 @@ dependencies:
114
120
  - - "<="
115
121
  - !ruby/object:Gem::Version
116
122
  version: '6.0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: pry
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
117
137
  description: Check the home page for more in-depth information.
118
138
  email:
119
139
  - e@zzak.io
@@ -121,6 +141,7 @@ executables: []
121
141
  extensions: []
122
142
  extra_rdoc_files: []
123
143
  files:
144
+ - CHANGELOG.md
124
145
  - LICENSE
125
146
  - README.md
126
147
  - lib/acts_as_paranoid.rb
@@ -157,8 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
178
  - !ruby/object:Gem::Version
158
179
  version: '0'
159
180
  requirements: []
160
- rubyforge_project:
161
- rubygems_version: 2.7.6
181
+ rubygems_version: 3.1.2
162
182
  signing_key:
163
183
  specification_version: 4
164
184
  summary: Active Record plugin which allows you to hide and restore records without