paranoia 2.4.3 → 3.0.1

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: d0174a966648c3afbf8ce8d09ef65361c48540838e705bb3327477f36ee27eff
4
- data.tar.gz: 70a9eb72fa2cfda78c4adf5aa9918404ad5860d725ac657959f0b105fd300276
3
+ metadata.gz: 8e7ad759d079b1e2bea13d47f092005677c98dee88423a12666fc77db15e8249
4
+ data.tar.gz: f95c4af13b076d6d235d3b6d0b7057595742a8dcd177e71b0ada70cb11674f91
5
5
  SHA512:
6
- metadata.gz: 2a960433d0574c324778ae6774cc90501ee3a108c91e8aca45f91eeb59b1a80406b861b94e68b7fe391dc50c32fb7ebc034aa43296ec6ffd3bb78e863051a067
7
- data.tar.gz: 765b1c436d256277a0ac54947584b90c69ffec5bad08011696e4a62267d1bf1e3b782957d904425933083b12e1fa3ce2668872acc83e04877304243ea96f9a61
6
+ metadata.gz: 9b42a61dde0a3e2c417f2d7a0c71c23ad4b1c6980fe4a0a232313625017fe5c5e24065405a28e202bb3751d34e379753ae70a908d552f6704c7b1e78fb18862a
7
+ data.tar.gz: 45fc5b4fe5c4705f29e9bd208dd18d1fcec907c79649167467dc76ae883e002da9b2db93ac41148ab7a47872a39aa3c7545c754e7627dd8b5348e519654806ee
@@ -0,0 +1,40 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: build
9
+
10
+ on: [push, pull_request]
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-20.04
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ rails: ["~> 7.2.0", "~> 7.1.0", "~> 7.0.0", "~> 6.1.0"]
19
+ ruby: ["3.3","3.2", "3.1", "3.0", "2.7"]
20
+ exclude:
21
+ - rails: "~> 7.2.0"
22
+ ruby: "3.0"
23
+ - rails: "~> 7.2.0"
24
+ ruby: "2.7"
25
+ - rails: "edge"
26
+ ruby: "3.0"
27
+ - rails: "edge"
28
+ ruby: "2.7"
29
+
30
+
31
+
32
+ env:
33
+ RAILS: ${{ matrix.rails }}
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: ${{ matrix.ruby }}
39
+ bundler-cache: true
40
+ - run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,5 +1,98 @@
1
1
  # paranoia Changelog
2
2
 
3
+ ## 3.0.1 - January 19, 2025
4
+
5
+ - [#566](https://github.com/rubysherpas/paranoia/pull/566) Handle #delete_all
6
+ - [#559](https://github.com/rubysherpas/paranoia/pull/559) Trigger an after_commit callback when restoring a record
7
+ - [#567](https://github.com/rubysherpas/paranoia/pull/567) Fix typo in newly added readme
8
+
9
+ ## 3.0.0 - August 13, 2024
10
+
11
+ _Tagged as 3.0 as Ruby + Rails version constraints have been modernised._
12
+
13
+ - [#564](https://github.com/rubysherpas/paranoia/pull/564) Support Rails edge
14
+ - [#563](https://github.com/rubysherpas/paranoia/pull/563) Support Rails 7.2
15
+
16
+ ## 2.6.4 - July 20, 2024
17
+
18
+ * [#554](https://github.com/rubysherpas/paranoia/pull/554) Support prebuilt counter cache association list (#554)
19
+ [Joé Dupuis](https://github.com/JoeDupuis)
20
+ * [#551](https://github.com/rubysherpas/paranoia/pull/551) Fix: restore has_one with scope (#551)
21
+ [Paweł Charyło](https://github.com/zygzagZ)
22
+ * [#555](https://github.com/rubysherpas/paranoia/pull/555) 📝 Add Yard documentation for Paranoia::Query (#555)
23
+ [Clément Prod'homme](https://github.com/cprodhomme)
24
+
25
+ ## 2.6.3 - Oct 12, 2023
26
+
27
+ * [#548](https://github.com/rubysherpas/paranoia/pull/548) Add support for [Rails 7.1](https://github.com/rails/rails/releases/tag/v7.1.0) (#548)
28
+ [Indyarocks](https://github.com/indyarocks)
29
+
30
+ ## 2.6.2 - Jun 6, 2023
31
+
32
+ * [#441](https://github.com/rubysherpas/paranoia/pull/441) Recursive restore with has_many/one through assocs (#441)
33
+ [Emil Ong](https://github.com/emilong)
34
+
35
+ ## 2.6.1 - Nov 16, 2022
36
+
37
+ * [#535](https://github.com/rubysherpas/paranoia/pull/535) Allow to skip updating paranoia_destroy_attributes for records while really_destroy!
38
+ [Anton Bogdanov](https://github.com/kortirso)
39
+
40
+ ## 2.6.0 - Mar 23, 2022
41
+
42
+ * [#512](https://github.com/rubysherpas/paranoia/pull/512) Quote table names; Mysql 8 has keywords that might match table names which cause an exception.
43
+ * [#476](https://github.com/rubysherpas/paranoia/pull/476) Fix syntax error in documentation.
44
+ * [#485](https://github.com/rubysherpas/paranoia/pull/485) Rollback transaction if destroy aborted.
45
+ * [#522](https://github.com/rubysherpas/paranoia/pull/522) Add failing tests for association with abort on destroy.
46
+ * [#513](https://github.com/rubysherpas/paranoia/pull/513) Fix create callback called on destroy.
47
+
48
+ ## 2.5.3
49
+
50
+ * [#532](https://github.com/rubysherpas/paranoia/pull/532) Fix: correct bug when sentinel_value is not a timestamp
51
+ [Hassanin Ahmed](https://github.com/sas1ni69)
52
+ * [#531](https://github.com/rubysherpas/paranoia/pull/531) Added test case to reproduce bug introduce in v2.5.1
53
+ [Sherif Elkassaby](https://github.com/sherif-nedap)
54
+ * [#529](https://github.com/rubysherpas/paranoia/pull/529) Fix: Do not define a RSpec matcher when RSpec isn't present
55
+ [Sebastian Welther](https://github.com/swelther)
56
+
57
+ ## 2.5.2
58
+
59
+ * [#526](https://github.com/rubysherpas/paranoia/pull/526) Do not include tests files in packaged gem
60
+
61
+ [Jason Fleetwood-Boldt](https://github.com/jasonfb)
62
+ * [#492](https://github.com/rubysherpas/paranoia/pull/492) Warn if acts_as_paranoid is called more than once on the same model
63
+
64
+ [Ignatius Reza](https://github.com/ignatiusreza)
65
+
66
+ ## 2.5.1
67
+
68
+ * [#481](https://github.com/rubysherpas/paranoia/pull/481) Replaces hard coded `deleted_at` with `paranoia_column`.
69
+
70
+ [Hassanin Ahmed](https://github.com/sas1ni69)
71
+
72
+ ## 2.5.0
73
+
74
+ * [#516](https://github.com/rubysherpas/paranoia/pull/516) Add support for ActiveRecord 7.0, drop support for EOL Ruby < 2.5 and Rails < 5.1
75
+ adding support for Rails 7
76
+
77
+ [Mathieu Jobin](https://github.com/mathieujobin)
78
+ * [#515](https://github.com/rubysherpas/paranoia/pull/515) Switch from Travis CI to GitHub Actions
79
+
80
+ [Shinichi Maeshima](https://github.com/willnet)
81
+
82
+ ## 2.4.3
83
+
84
+ * [#503](https://github.com/rubysherpas/paranoia/pull/503) Bump activerecord dependency for Rails 6.1
85
+
86
+ [Jörg Schiller](https://github.com/joergschiller)
87
+
88
+ * [#483](https://github.com/rubysherpas/paranoia/pull/483) Update JRuby version to 9.2.8.0 + remove EOL Ruby 2.2
89
+
90
+ [Uwe Kubosch](https://github.com/donv)
91
+
92
+ * [#482](https://github.com/rubysherpas/paranoia/pull/482) Fix after_commit for Rails 6
93
+
94
+ [Ashwin Hegde](https://github.com/hashwin)
95
+
3
96
  ## 2.4.2
4
97
 
5
98
  * [#470](https://github.com/rubysherpas/paranoia/pull/470) Add support for ActiveRecord 6.0
data/Gemfile CHANGED
@@ -5,22 +5,24 @@ sqlite = ENV['SQLITE_VERSION']
5
5
  if sqlite
6
6
  gem 'sqlite3', sqlite, platforms: [:ruby]
7
7
  else
8
- gem 'sqlite3', platforms: [:ruby]
8
+ gem 'sqlite3', '~> 1.4', platforms: [:ruby]
9
9
  end
10
10
 
11
11
  platforms :jruby do
12
12
  gem 'activerecord-jdbcsqlite3-adapter'
13
13
  end
14
14
 
15
- platforms :rbx do
16
- gem 'rubinius-developer_tools'
17
- gem 'rubysl', '~> 2.0'
18
- gem 'rubysl-test-unit'
15
+ if RUBY_ENGINE == 'rbx'
16
+ platforms :rbx do
17
+ gem 'rubinius-developer_tools'
18
+ gem 'rubysl', '~> 2.0'
19
+ gem 'rubysl-test-unit'
20
+ end
19
21
  end
20
22
 
21
- rails = ENV['RAILS'] || '~> 5.2.0'
23
+ rails = ENV['RAILS'] || '~> 6.0.4'
22
24
 
23
- if rails == 'master'
25
+ if rails == 'edge'
24
26
  gem 'rails', github: 'rails/rails'
25
27
  else
26
28
  gem 'rails', rails
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- **Notice:**
1
+ [![Gem Version](https://badge.fury.io/rb/paranoia.svg)](https://badge.fury.io/rb/paranoia)
2
+ [![build](https://github.com/rubysherpas/paranoia/actions/workflows/build.yml/badge.svg)](https://github.com/rubysherpas/paranoia/actions/workflows/build.yml)
3
+
4
+ **Notice:**
2
5
 
3
6
  `paranoia` has some surprising behaviour (like overriding ActiveRecord's `delete` and `destroy`) and is not recommended for new projects. See [`discard`'s README](https://github.com/jhawthorn/discard#why-not-paranoia-or-acts_as_paranoid) for more details.
4
7
 
@@ -100,6 +103,14 @@ If you really want it gone *gone*, call `really_destroy!`:
100
103
  # => client
101
104
  ```
102
105
 
106
+ If you need skip updating timestamps for deleting records, call `really_destroy!(update_destroy_attributes: false)`.
107
+ When we call `really_destroy!(update_destroy_attributes: false)` on the parent `client`, then each child `email` will also have `really_destroy!(update_destroy_attributes: false)` called.
108
+
109
+ ``` ruby
110
+ >> client.really_destroy!(update_destroy_attributes: false)
111
+ # => client
112
+ ```
113
+
103
114
  If you want to use a column other than `deleted_at`, you can pass it as an option:
104
115
 
105
116
  ``` ruby
@@ -190,11 +201,24 @@ client.restore(:recursive => true)
190
201
  If you want to restore a record and only those dependently destroyed associated records that were deleted within 2 minutes of the object upon which they depend:
191
202
 
192
203
  ``` ruby
193
- Client.restore(id, :recursive => true. :recovery_window => 2.minutes)
204
+ Client.restore(id, :recursive => true, :recovery_window => 2.minutes)
194
205
  # or
195
206
  client.restore(:recursive => true, :recovery_window => 2.minutes)
196
207
  ```
197
208
 
209
+ If you want to trigger an after_commit callback when restoring a record:
210
+
211
+ ``` ruby
212
+ class Client < ActiveRecord::Base
213
+ acts_as_paranoid after_restore_commit: true
214
+
215
+ after_commit :commit_called, on: :restore
216
+ # or
217
+ after_restore_commit :commit_called
218
+ ...
219
+ end
220
+ ```
221
+
198
222
  Note that by default paranoia will not prevent that a soft destroyed object can't be associated with another object of a different model.
199
223
  A Rails validator is provided should you require this functionality:
200
224
  ``` ruby
@@ -337,6 +361,21 @@ end
337
361
  # => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>
338
362
  ```
339
363
 
364
+ #### delete_all:
365
+
366
+ The gem supports `delete_all` method, however it is disabled by default, to enable it add this in your `environment` file
367
+
368
+ ``` ruby
369
+ Paranoia.delete_all_enabled = true
370
+ ```
371
+ alternatively, you can enable/disable it for specific models as follow:
372
+
373
+ ``` ruby
374
+ class User < ActiveRecord::Base
375
+ acts_as_paranoid(delete_all_enabled: true)
376
+ end
377
+ ```
378
+
340
379
  ## Acts As Paranoid Migration
341
380
 
342
381
  You can replace the older `acts_as_paranoid` methods as follows:
@@ -1,23 +1,26 @@
1
- require 'rspec/expectations'
1
+ if defined?(RSpec)
2
+ require 'rspec/expectations'
2
3
 
3
- # Validate the subject's class did call "acts_as_paranoid"
4
- RSpec::Matchers.define :act_as_paranoid do
5
- match { |subject| subject.class.ancestors.include?(Paranoia) }
4
+ # Validate the subject's class did call "acts_as_paranoid"
5
+ RSpec::Matchers.define :act_as_paranoid do
6
+ match { |subject| subject.class.ancestors.include?(Paranoia) }
6
7
 
7
- failure_message_proc = lambda do
8
- "expected #{subject.class} to use `acts_as_paranoid`"
9
- end
8
+ failure_message_proc = lambda do
9
+ "expected #{subject.class} to use `acts_as_paranoid`"
10
+ end
10
11
 
11
- failure_message_when_negated_proc = lambda do
12
- "expected #{subject.class} not to use `acts_as_paranoid`"
13
- end
12
+ failure_message_when_negated_proc = lambda do
13
+ "expected #{subject.class} not to use `acts_as_paranoid`"
14
+ end
14
15
 
15
- if respond_to?(:failure_message_when_negated)
16
- failure_message(&failure_message_proc)
17
- failure_message_when_negated(&failure_message_when_negated_proc)
18
- else
19
- # RSpec 2 compatibility:
20
- failure_message_for_should(&failure_message_proc)
21
- failure_message_for_should_not(&failure_message_when_negated_proc)
16
+ if respond_to?(:failure_message_when_negated)
17
+ failure_message(&failure_message_proc)
18
+ failure_message_when_negated(&failure_message_when_negated_proc)
19
+ else
20
+ # RSpec 2 compatibility:
21
+ failure_message_for_should(&failure_message_proc)
22
+ failure_message_for_should_not(&failure_message_when_negated_proc)
23
+ end
22
24
  end
25
+
23
26
  end
@@ -1,3 +1,3 @@
1
1
  module Paranoia
2
- VERSION = '2.4.3'.freeze
2
+ VERSION = '3.0.1'.freeze
3
3
  end
data/lib/paranoia.rb CHANGED
@@ -6,15 +6,11 @@ if [ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR] == [5, 2] ||
6
6
  end
7
7
 
8
8
  module Paranoia
9
- @@default_sentinel_value = nil
10
9
 
11
- # Change default_sentinel_value in a rails initializer
12
- def self.default_sentinel_value=(val)
13
- @@default_sentinel_value = val
14
- end
15
-
16
- def self.default_sentinel_value
17
- @@default_sentinel_value
10
+ class << self
11
+ # Change default values in a rails initializer
12
+ attr_accessor :default_sentinel_value,
13
+ :delete_all_enabled
18
14
  end
19
15
 
20
16
  def self.included(klazz)
@@ -24,6 +20,7 @@ module Paranoia
24
20
  module Query
25
21
  def paranoid? ; true ; end
26
22
 
23
+ # If you want to find all records, even those which are deleted
27
24
  def with_deleted
28
25
  if ActiveRecord::VERSION::STRING >= "4.1"
29
26
  return unscope where: paranoia_column
@@ -31,6 +28,7 @@ module Paranoia
31
28
  all.tap { |x| x.default_scoped = false }
32
29
  end
33
30
 
31
+ # If you want to find only the deleted records
34
32
  def only_deleted
35
33
  if paranoia_sentinel_value.nil?
36
34
  return with_deleted.where.not(paranoia_column => paranoia_sentinel_value)
@@ -40,11 +38,12 @@ module Paranoia
40
38
  # these will not match != sentinel value because "NULL != value" is
41
39
  # NULL under the sql standard
42
40
  # Scoping with the table_name is mandatory to avoid ambiguous errors when joining tables.
43
- scoped_quoted_paranoia_column = "#{self.table_name}.#{connection.quote_column_name(paranoia_column)}"
41
+ scoped_quoted_paranoia_column = "#{connection.quote_table_name(self.table_name)}.#{connection.quote_column_name(paranoia_column)}"
44
42
  with_deleted.where("#{scoped_quoted_paranoia_column} IS NULL OR #{scoped_quoted_paranoia_column} != ?", paranoia_sentinel_value)
45
43
  end
46
44
  alias_method :deleted, :only_deleted
47
45
 
46
+ # If you want to restore a record
48
47
  def restore(id_or_ids, opts = {})
49
48
  ids = Array(id_or_ids).flatten
50
49
  any_object_instead_of_id = ids.any? { |id| ActiveRecord::Base === id }
@@ -55,12 +54,22 @@ module Paranoia
55
54
  end
56
55
  ids.map { |id| only_deleted.find(id).restore!(opts) }
57
56
  end
57
+
58
+ def paranoia_destroy_attributes
59
+ {
60
+ paranoia_column => current_time_from_proper_timezone
61
+ }.merge(timestamp_attributes_with_current_time)
62
+ end
63
+
64
+ def timestamp_attributes_with_current_time
65
+ timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
66
+ end
58
67
  end
59
68
 
60
69
  def paranoia_destroy
61
- transaction do
62
- run_callbacks(:destroy) do
63
- @_disable_counter_cache = deleted?
70
+ with_transaction_returning_status do
71
+ result = run_callbacks(:destroy) do
72
+ @_disable_counter_cache = paranoia_destroyed?
64
73
  result = paranoia_delete
65
74
  next result unless result && ActiveRecord::VERSION::STRING >= '4.2'
66
75
  each_counter_cached_associations do |association|
@@ -73,7 +82,9 @@ module Paranoia
73
82
  @_disable_counter_cache = false
74
83
  result
75
84
  end
76
- end
85
+ raise ActiveRecord::Rollback, "Not destroyed" unless paranoia_destroyed?
86
+ result
87
+ end || false
77
88
  end
78
89
  alias_method :destroy, :paranoia_destroy
79
90
 
@@ -83,7 +94,16 @@ module Paranoia
83
94
  end
84
95
 
85
96
  def trigger_transactional_callbacks?
86
- super || @_trigger_destroy_callback && paranoia_destroyed?
97
+ super || @_trigger_destroy_callback && paranoia_destroyed? ||
98
+ @_trigger_restore_callback && !paranoia_destroyed?
99
+ end
100
+
101
+ def transaction_include_any_action?(actions)
102
+ super || actions.any? do |action|
103
+ if action == :restore
104
+ paranoia_after_restore_commit && @_trigger_restore_callback
105
+ end
106
+ end
87
107
  end
88
108
 
89
109
  def paranoia_delete
@@ -110,6 +130,10 @@ module Paranoia
110
130
  if within_recovery_window?(recovery_window_range) && ((noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen)
111
131
  @_disable_counter_cache = !paranoia_destroyed?
112
132
  write_attribute paranoia_column, paranoia_sentinel_value
133
+ if paranoia_after_restore_commit
134
+ @_trigger_restore_callback = true
135
+ add_to_transaction
136
+ end
113
137
  update_columns(paranoia_restore_attributes)
114
138
  each_counter_cached_associations do |association|
115
139
  if send(association.reflection.name)
@@ -123,27 +147,31 @@ module Paranoia
123
147
  end
124
148
 
125
149
  self
150
+ ensure
151
+ if paranoia_after_restore_commit
152
+ @_trigger_restore_callback = false
153
+ end
126
154
  end
127
155
  alias :restore :restore!
128
156
 
129
157
  def get_recovery_window_range(opts)
130
158
  return opts[:recovery_window_range] if opts[:recovery_window_range]
131
159
  return unless opts[:recovery_window]
132
- (deleted_at - opts[:recovery_window]..deleted_at + opts[:recovery_window])
160
+ (deletion_time - opts[:recovery_window]..deletion_time + opts[:recovery_window])
133
161
  end
134
162
 
135
163
  def within_recovery_window?(recovery_window_range)
136
164
  return true unless recovery_window_range
137
- recovery_window_range.cover?(deleted_at)
165
+ recovery_window_range.cover?(deletion_time)
138
166
  end
139
167
 
140
168
  def paranoia_destroyed?
141
- send(paranoia_column) != paranoia_sentinel_value
169
+ paranoia_column_value != paranoia_sentinel_value
142
170
  end
143
171
  alias :deleted? :paranoia_destroyed?
144
172
 
145
- def really_destroy!
146
- transaction do
173
+ def really_destroy!(update_destroy_attributes: true)
174
+ with_transaction_returning_status do
147
175
  run_callbacks(:real_destroy) do
148
176
  @_disable_counter_cache = paranoia_destroyed?
149
177
  dependent_reflections = self.class.reflections.select do |name, reflection|
@@ -156,12 +184,14 @@ module Paranoia
156
184
  # .paranoid? will work for both instances and classes
157
185
  next unless association_data && association_data.paranoid?
158
186
  if reflection.collection?
159
- next association_data.with_deleted.each(&:really_destroy!)
187
+ next association_data.with_deleted.find_each { |record|
188
+ record.really_destroy!(update_destroy_attributes: update_destroy_attributes)
189
+ }
160
190
  end
161
- association_data.really_destroy!
191
+ association_data.really_destroy!(update_destroy_attributes: update_destroy_attributes)
162
192
  end
163
193
  end
164
- update_columns(paranoia_destroy_attributes)
194
+ update_columns(paranoia_destroy_attributes) if update_destroy_attributes
165
195
  destroy_without_paranoia
166
196
  end
167
197
  end
@@ -169,24 +199,43 @@ module Paranoia
169
199
 
170
200
  private
171
201
 
202
+ def counter_cache_disabled?
203
+ defined?(@_disable_counter_cache) && @_disable_counter_cache
204
+ end
205
+
206
+ def counter_cached_association_names
207
+ return [] if counter_cache_disabled?
208
+ super
209
+ end
210
+
172
211
  def each_counter_cached_associations
173
- !(defined?(@_disable_counter_cache) && @_disable_counter_cache) ? super : []
212
+ return [] if counter_cache_disabled?
213
+
214
+ if defined?(super)
215
+ super
216
+ else
217
+ counter_cached_association_names.each do |name|
218
+ yield association(name)
219
+ end
220
+ end
174
221
  end
175
222
 
176
223
  def paranoia_restore_attributes
177
224
  {
178
225
  paranoia_column => paranoia_sentinel_value
179
- }.merge(timestamp_attributes_with_current_time)
226
+ }.merge(self.class.timestamp_attributes_with_current_time)
180
227
  end
181
228
 
182
- def paranoia_destroy_attributes
183
- {
184
- paranoia_column => current_time_from_proper_timezone
185
- }.merge(timestamp_attributes_with_current_time)
186
- end
229
+ delegate :paranoia_destroy_attributes, to: 'self.class'
187
230
 
188
- def timestamp_attributes_with_current_time
189
- timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
231
+ def paranoia_find_has_one_target(association)
232
+ association_foreign_key = association.options[:through].present? ? association.klass.primary_key : association.foreign_key
233
+ association_find_conditions = { association_foreign_key => self.id }
234
+ association_find_conditions[association.type] = self.class.name if association.type
235
+
236
+ scope = association.klass.only_deleted.where(association_find_conditions)
237
+ scope = scope.merge(association.scope) if association.scope
238
+ scope.first
190
239
  end
191
240
 
192
241
  # restore associated records that have been soft deleted when
@@ -212,42 +261,70 @@ module Paranoia
212
261
  end
213
262
 
214
263
  if association_data.nil? && association.macro.to_s == "has_one"
215
- association_class_name = association.klass.name
216
- association_foreign_key = association.foreign_key
217
-
218
- if association.type
219
- association_polymorphic_type = association.type
220
- association_find_conditions = { association_polymorphic_type => self.class.name.to_s, association_foreign_key => self.id }
221
- else
222
- association_find_conditions = { association_foreign_key => self.id }
223
- end
224
-
225
- association_class = association_class_name.constantize
226
- if association_class.paranoid?
227
- association_class.only_deleted.where(association_find_conditions).first
264
+ if association.klass.paranoid?
265
+ paranoia_find_has_one_target(association)
228
266
  .try!(:restore, recursive: true, :recovery_window_range => recovery_window_range)
229
267
  end
230
268
  end
231
269
  end
232
270
 
233
- clear_association_cache if destroyed_associations.present?
271
+ if ActiveRecord.version.to_s > '7'
272
+ # Method deleted in https://github.com/rails/rails/commit/dd5886d00a2d5f31ccf504c391aad93deb014eb8
273
+ @association_cache.clear if persisted? && destroyed_associations.present?
274
+ else
275
+ clear_association_cache if destroyed_associations.present?
276
+ end
277
+ end
278
+ end
279
+
280
+ module ActiveRecord
281
+ module Transactions
282
+ module RestoreSupport
283
+ def self.included(base)
284
+ base::ACTIONS << :restore unless base::ACTIONS.include?(:restore)
285
+ end
286
+ end
287
+
288
+ module ClassMethods
289
+ def after_restore_commit(*args, &block)
290
+ set_options_for_callbacks!(args, on: :restore)
291
+ set_callback(:commit, :after, *args, &block)
292
+ end
293
+ end
234
294
  end
235
295
  end
236
296
 
297
+ module Paranoia::Relation
298
+ def paranoia_delete_all
299
+ update_all(klass.paranoia_destroy_attributes)
300
+ end
301
+
302
+ alias_method :delete_all, :paranoia_delete_all
303
+ end
304
+
237
305
  ActiveSupport.on_load(:active_record) do
238
306
  class ActiveRecord::Base
239
307
  def self.acts_as_paranoid(options={})
308
+ if included_modules.include?(Paranoia)
309
+ puts "[WARN] #{self.name} is calling acts_as_paranoid more than once!"
310
+
311
+ return
312
+ end
313
+
240
314
  define_model_callbacks :restore, :real_destroy
241
315
 
242
316
  alias_method :really_destroyed?, :destroyed?
243
317
  alias_method :really_delete, :delete
244
318
  alias_method :destroy_without_paranoia, :destroy
319
+ class << self; delegate :really_delete_all, to: :all end
245
320
 
246
321
  include Paranoia
247
- class_attribute :paranoia_column, :paranoia_sentinel_value
322
+ class_attribute :paranoia_column, :paranoia_sentinel_value, :paranoia_after_restore_commit,
323
+ :delete_all_enabled
248
324
 
249
325
  self.paranoia_column = (options[:column] || :deleted_at).to_s
250
326
  self.paranoia_sentinel_value = options.fetch(:sentinel_value) { Paranoia.default_sentinel_value }
327
+ self.paranoia_after_restore_commit = options.fetch(:after_restore_commit) { false }
251
328
  def self.paranoia_scope
252
329
  where(paranoia_column => paranoia_sentinel_value)
253
330
  end
@@ -263,6 +340,20 @@ ActiveSupport.on_load(:active_record) do
263
340
  after_restore {
264
341
  self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers)
265
342
  }
343
+
344
+ if paranoia_after_restore_commit
345
+ ActiveRecord::Transactions.send(:include, ActiveRecord::Transactions::RestoreSupport)
346
+ end
347
+
348
+ self.delete_all_enabled = options[:delete_all_enabled] || Paranoia.delete_all_enabled
349
+
350
+ if self.delete_all_enabled
351
+ "#{self}::ActiveRecord_Relation".constantize.class_eval do
352
+ alias_method :really_delete_all, :delete_all
353
+
354
+ include Paranoia::Relation
355
+ end
356
+ end
266
357
  end
267
358
 
268
359
  # Please do not use this method in production.
@@ -285,9 +376,17 @@ ActiveSupport.on_load(:active_record) do
285
376
  self.class.paranoia_column
286
377
  end
287
378
 
379
+ def paranoia_column_value
380
+ send(paranoia_column)
381
+ end
382
+
288
383
  def paranoia_sentinel_value
289
384
  self.class.paranoia_sentinel_value
290
385
  end
386
+
387
+ def deletion_time
388
+ paranoia_column_value.acts_like?(:time) ? paranoia_column_value : deleted_at
389
+ end
291
390
  end
292
391
  end
293
392