paranoia 2.2.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/paranoia.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require 'active_record' unless defined? ActiveRecord
2
2
 
3
+ if [ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR] == [5, 2] ||
4
+ ActiveRecord::VERSION::MAJOR > 5
5
+ require 'paranoia/active_record_5_2'
6
+ end
7
+
3
8
  module Paranoia
4
9
  @@default_sentinel_value = nil
5
10
 
@@ -14,7 +19,6 @@ module Paranoia
14
19
 
15
20
  def self.included(klazz)
16
21
  klazz.extend Query
17
- klazz.extend Callbacks
18
22
  end
19
23
 
20
24
  module Query
@@ -36,7 +40,7 @@ module Paranoia
36
40
  # these will not match != sentinel value because "NULL != value" is
37
41
  # NULL under the sql standard
38
42
  # Scoping with the table_name is mandatory to avoid ambiguous errors when joining tables.
39
- scoped_quoted_paranoia_column = "#{self.table_name}.#{connection.quote_column_name(paranoia_column)}"
43
+ scoped_quoted_paranoia_column = "#{connection.quote_table_name(self.table_name)}.#{connection.quote_column_name(paranoia_column)}"
40
44
  with_deleted.where("#{scoped_quoted_paranoia_column} IS NULL OR #{scoped_quoted_paranoia_column} != ?", paranoia_sentinel_value)
41
45
  end
42
46
  alias_method :deleted, :only_deleted
@@ -53,30 +57,11 @@ module Paranoia
53
57
  end
54
58
  end
55
59
 
56
- module Callbacks
57
- def self.extended(klazz)
58
- [:restore, :real_destroy].each do |callback_name|
59
- klazz.define_callbacks callback_name
60
-
61
- klazz.define_singleton_method("before_#{callback_name}") do |*args, &block|
62
- set_callback(callback_name, :before, *args, &block)
63
- end
64
-
65
- klazz.define_singleton_method("around_#{callback_name}") do |*args, &block|
66
- set_callback(callback_name, :around, *args, &block)
67
- end
68
-
69
- klazz.define_singleton_method("after_#{callback_name}") do |*args, &block|
70
- set_callback(callback_name, :after, *args, &block)
71
- end
72
- end
73
- end
74
- end
75
-
76
- def destroy
77
- transaction do
78
- run_callbacks(:destroy) do
79
- result = delete
60
+ def paranoia_destroy
61
+ with_transaction_returning_status do
62
+ result = run_callbacks(:destroy) do
63
+ @_disable_counter_cache = deleted?
64
+ result = paranoia_delete
80
65
  next result unless result && ActiveRecord::VERSION::STRING >= '4.2'
81
66
  each_counter_cached_associations do |association|
82
67
  foreign_key = association.reflection.foreign_key.to_sym
@@ -84,12 +69,26 @@ module Paranoia
84
69
  next unless send(association.reflection.name)
85
70
  association.decrement_counters
86
71
  end
72
+ @_trigger_destroy_callback = true
73
+ @_disable_counter_cache = false
87
74
  result
88
75
  end
89
- end
76
+ raise ActiveRecord::Rollback, "Not destroyed" unless self.deleted?
77
+ result
78
+ end || false
79
+ end
80
+ alias_method :destroy, :paranoia_destroy
81
+
82
+ def paranoia_destroy!
83
+ paranoia_destroy ||
84
+ raise(ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record", self))
90
85
  end
91
86
 
92
- def delete
87
+ def trigger_transactional_callbacks?
88
+ super || @_trigger_destroy_callback && paranoia_destroyed?
89
+ end
90
+
91
+ def paranoia_delete
93
92
  raise ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
94
93
  if persisted?
95
94
  # if a transaction exists, add the record so that after_commit
@@ -101,18 +100,27 @@ module Paranoia
101
100
  end
102
101
  self
103
102
  end
103
+ alias_method :delete, :paranoia_delete
104
104
 
105
105
  def restore!(opts = {})
106
106
  self.class.transaction do
107
107
  run_callbacks(:restore) do
108
+ recovery_window_range = get_recovery_window_range(opts)
108
109
  # Fixes a bug where the build would error because attributes were frozen.
109
110
  # This only happened on Rails versions earlier than 4.1.
110
111
  noop_if_frozen = ActiveRecord.version < Gem::Version.new("4.1")
111
- if (noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen
112
+ if within_recovery_window?(recovery_window_range) && ((noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen)
113
+ @_disable_counter_cache = !paranoia_destroyed?
112
114
  write_attribute paranoia_column, paranoia_sentinel_value
113
115
  update_columns(paranoia_restore_attributes)
116
+ each_counter_cached_associations do |association|
117
+ if send(association.reflection.name)
118
+ association.increment_counters
119
+ end
120
+ end
121
+ @_disable_counter_cache = false
114
122
  end
115
- restore_associated_records if opts[:recursive]
123
+ restore_associated_records(recovery_window_range) if opts[:recursive]
116
124
  end
117
125
  end
118
126
 
@@ -120,14 +128,26 @@ module Paranoia
120
128
  end
121
129
  alias :restore :restore!
122
130
 
131
+ def get_recovery_window_range(opts)
132
+ return opts[:recovery_window_range] if opts[:recovery_window_range]
133
+ return unless opts[:recovery_window]
134
+ (deletion_time - opts[:recovery_window]..deletion_time + opts[:recovery_window])
135
+ end
136
+
137
+ def within_recovery_window?(recovery_window_range)
138
+ return true unless recovery_window_range
139
+ recovery_window_range.cover?(deletion_time)
140
+ end
141
+
123
142
  def paranoia_destroyed?
124
- send(paranoia_column) != paranoia_sentinel_value
143
+ paranoia_column_value != paranoia_sentinel_value
125
144
  end
126
145
  alias :deleted? :paranoia_destroyed?
127
146
 
128
147
  def really_destroy!
129
- transaction do
148
+ with_transaction_returning_status do
130
149
  run_callbacks(:real_destroy) do
150
+ @_disable_counter_cache = paranoia_destroyed?
131
151
  dependent_reflections = self.class.reflections.select do |name, reflection|
132
152
  reflection.options[:dependent] == :destroy
133
153
  end
@@ -143,7 +163,7 @@ module Paranoia
143
163
  association_data.really_destroy!
144
164
  end
145
165
  end
146
- write_attribute(paranoia_column, current_time_from_proper_timezone)
166
+ update_columns(paranoia_destroy_attributes)
147
167
  destroy_without_paranoia
148
168
  end
149
169
  end
@@ -151,6 +171,10 @@ module Paranoia
151
171
 
152
172
  private
153
173
 
174
+ def each_counter_cached_associations
175
+ !(defined?(@_disable_counter_cache) && @_disable_counter_cache) ? super : []
176
+ end
177
+
154
178
  def paranoia_restore_attributes
155
179
  {
156
180
  paranoia_column => paranoia_sentinel_value
@@ -169,7 +193,7 @@ module Paranoia
169
193
 
170
194
  # restore associated records that have been soft deleted when
171
195
  # we called #destroy
172
- def restore_associated_records
196
+ def restore_associated_records(recovery_window_range = nil)
173
197
  destroyed_associations = self.class.reflect_on_all_associations.select do |association|
174
198
  association.options[:dependent] == :destroy
175
199
  end
@@ -180,9 +204,11 @@ module Paranoia
180
204
  unless association_data.nil?
181
205
  if association_data.paranoid?
182
206
  if association.collection?
183
- association_data.only_deleted.each { |record| record.restore(:recursive => true) }
207
+ association_data.only_deleted.each do |record|
208
+ record.restore(:recursive => true, :recovery_window_range => recovery_window_range)
209
+ end
184
210
  else
185
- association_data.restore(:recursive => true)
211
+ association_data.restore(:recursive => true, :recovery_window_range => recovery_window_range)
186
212
  end
187
213
  end
188
214
  end
@@ -200,18 +226,32 @@ module Paranoia
200
226
 
201
227
  association_class = association_class_name.constantize
202
228
  if association_class.paranoid?
203
- association_class.only_deleted.where(association_find_conditions).first.try!(:restore, recursive: true)
229
+ association_class.only_deleted.where(association_find_conditions).first
230
+ .try!(:restore, recursive: true, :recovery_window_range => recovery_window_range)
204
231
  end
205
232
  end
206
233
  end
207
234
 
208
- clear_association_cache if destroyed_associations.present?
235
+ if ActiveRecord.version.to_s > '7'
236
+ # Method deleted in https://github.com/rails/rails/commit/dd5886d00a2d5f31ccf504c391aad93deb014eb8
237
+ @association_cache.clear if persisted? && destroyed_associations.present?
238
+ else
239
+ clear_association_cache if destroyed_associations.present?
240
+ end
209
241
  end
210
242
  end
211
243
 
212
244
  ActiveSupport.on_load(:active_record) do
213
245
  class ActiveRecord::Base
214
246
  def self.acts_as_paranoid(options={})
247
+ if included_modules.include?(Paranoia)
248
+ puts "[WARN] #{self.name} is calling acts_as_paranoid more than once!"
249
+
250
+ return
251
+ end
252
+
253
+ define_model_callbacks :restore, :real_destroy
254
+
215
255
  alias_method :really_destroyed?, :destroyed?
216
256
  alias_method :really_delete, :delete
217
257
  alias_method :destroy_without_paranoia, :destroy
@@ -258,9 +298,17 @@ ActiveSupport.on_load(:active_record) do
258
298
  self.class.paranoia_column
259
299
  end
260
300
 
301
+ def paranoia_column_value
302
+ send(paranoia_column)
303
+ end
304
+
261
305
  def paranoia_sentinel_value
262
306
  self.class.paranoia_sentinel_value
263
307
  end
308
+
309
+ def deletion_time
310
+ paranoia_column_value.acts_like?(:time) ? paranoia_column_value : deleted_at
311
+ end
264
312
  end
265
313
  end
266
314
 
@@ -269,8 +317,8 @@ require 'paranoia/rspec' if defined? RSpec
269
317
  module ActiveRecord
270
318
  module Validations
271
319
  module UniquenessParanoiaValidator
272
- def build_relation(klass, table, attribute, value)
273
- relation = super(klass, table, attribute, value)
320
+ def build_relation(klass, *args)
321
+ relation = super
274
322
  return relation unless klass.respond_to?(:paranoia_column)
275
323
  arel_paranoia_scope = klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value)
276
324
  if ActiveRecord::VERSION::STRING >= "5.0"
@@ -284,5 +332,14 @@ module ActiveRecord
284
332
  class UniquenessValidator < ActiveModel::EachValidator
285
333
  prepend UniquenessParanoiaValidator
286
334
  end
335
+
336
+ class AssociationNotSoftDestroyedValidator < ActiveModel::EachValidator
337
+ def validate_each(record, attribute, value)
338
+ # if association is soft destroyed, add an error
339
+ if value.present? && value.paranoia_destroyed?
340
+ record.errors.add(attribute, 'has been soft-deleted')
341
+ end
342
+ end
343
+ end
287
344
  end
288
345
  end
data/paranoia.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.license = 'MIT'
12
12
  s.summary = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5, using much, much, much less code."
13
13
  s.description = <<-DSC
14
- Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5,
14
+ Paranoia is a re-implementation of acts_as_paranoid for Rails 4, 5, 6, and 7,
15
15
  using much, much, much less code. You would use either plugin / gem if you
16
16
  wished that when you called destroy on an Active Record object that it
17
17
  didn't actually destroy it, but just "hid" the record. Paranoia does this
@@ -22,14 +22,19 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.required_rubygems_version = ">= 1.3.6"
24
24
 
25
- s.required_ruby_version = '>= 2.0'
25
+ s.required_ruby_version = '>= 2.5'
26
26
 
27
- s.add_dependency 'activerecord', '>= 4.0', '< 5.1'
27
+ s.add_dependency 'activerecord', '>= 5.1', '< 7.1'
28
28
 
29
29
  s.add_development_dependency "bundler", ">= 1.0.0"
30
30
  s.add_development_dependency "rake"
31
31
 
32
- s.files = `git ls-files`.split("\n")
32
+
33
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
34
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)}) }
35
+ files
36
+ end
37
+
33
38
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
34
39
  s.require_path = 'lib'
35
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paranoia
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.6.0
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: 2017-02-16 00:00:00.000000000 Z
11
+ date: 2022-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.0'
19
+ version: '5.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5.1'
22
+ version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '4.0'
29
+ version: '5.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5.1'
32
+ version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: bundler
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +59,7 @@ dependencies:
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  description: |2
62
- Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5,
62
+ Paranoia is a re-implementation of acts_as_paranoid for Rails 4, 5, 6, and 7,
63
63
  using much, much, much less code. You would use either plugin / gem if you
64
64
  wished that when you called destroy on an Active Record object that it
65
65
  didn't actually destroy it, but just "hid" the record. Paranoia does this
@@ -73,19 +73,20 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".github/workflows/build.yml"
76
77
  - ".gitignore"
77
- - ".travis.yml"
78
78
  - CHANGELOG.md
79
+ - CODE_OF_CONDUCT.md
79
80
  - CONTRIBUTING.md
80
81
  - Gemfile
81
82
  - LICENSE
82
83
  - README.md
83
84
  - Rakefile
84
85
  - lib/paranoia.rb
86
+ - lib/paranoia/active_record_5_2.rb
85
87
  - lib/paranoia/rspec.rb
86
88
  - lib/paranoia/version.rb
87
89
  - paranoia.gemspec
88
- - test/paranoia_test.rb
89
90
  homepage: https://github.com/rubysherpas/paranoia
90
91
  licenses:
91
92
  - MIT
@@ -98,15 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
99
  requirements:
99
100
  - - ">="
100
101
  - !ruby/object:Gem::Version
101
- version: '2.0'
102
+ version: '2.5'
102
103
  required_rubygems_version: !ruby/object:Gem::Requirement
103
104
  requirements:
104
105
  - - ">="
105
106
  - !ruby/object:Gem::Version
106
107
  version: 1.3.6
107
108
  requirements: []
108
- rubyforge_project:
109
- rubygems_version: 2.6.9
109
+ rubygems_version: 3.1.6
110
110
  signing_key:
111
111
  specification_version: 4
112
112
  summary: Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5,
data/.travis.yml DELETED
@@ -1,26 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.1.10
6
- - 2.2.6
7
- - 2.3.3
8
- - jruby-9.1.6.0
9
-
10
- env:
11
- matrix:
12
- - RAILS='~> 4.1.16'
13
- - RAILS='~> 4.2.7.1'
14
- - RAILS='~> 5.0.0.1'
15
-
16
- matrix:
17
- exclude:
18
- - env: RAILS='~> 5.0.0.1'
19
- rvm: 2.1.10
20
- allow_failures:
21
- - env: RAILS='~> 4.1.16'
22
- rvm: jruby-9.1.6.0
23
- - env: RAILS='~> 4.2.7.1'
24
- rvm: jruby-9.1.6.0
25
- - env: RAILS='~> 5.0.0.1'
26
- rvm: jruby-9.1.6.0