acts_as_paranoid 0.6.3 → 0.7.0

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.
@@ -1,13 +1,14 @@
1
- require 'acts_as_paranoid/core'
2
- require 'acts_as_paranoid/associations'
3
- require 'acts_as_paranoid/validations'
4
- require 'acts_as_paranoid/relation'
5
- require 'acts_as_paranoid/preloader_association'
1
+ # frozen_string_literal: true
6
2
 
7
- module ActsAsParanoid
3
+ require "active_record"
4
+ require "acts_as_paranoid/core"
5
+ require "acts_as_paranoid/associations"
6
+ require "acts_as_paranoid/validations"
7
+ require "acts_as_paranoid/relation"
8
8
 
9
+ module ActsAsParanoid
9
10
  def paranoid?
10
- self.included_modules.include?(ActsAsParanoid::Core)
11
+ included_modules.include?(ActsAsParanoid::Core)
11
12
  end
12
13
 
13
14
  def validates_as_paranoid
@@ -15,19 +16,29 @@ module ActsAsParanoid
15
16
  end
16
17
 
17
18
  def acts_as_paranoid(options = {})
18
- raise ArgumentError, "Hash expected, got #{options.class.name}" if not options.is_a?(Hash) and not options.empty?
19
+ if !options.is_a?(Hash) && !options.empty?
20
+ raise ArgumentError, "Hash expected, got #{options.class.name}"
21
+ end
19
22
 
20
23
  class_attribute :paranoid_configuration
21
24
 
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
- self.paranoid_configuration.merge!({ :deleted_value => "deleted" }) if options[:column_type] == "string"
24
- self.paranoid_configuration.merge!({ :allow_nulls => true }) if options[:column_type] == "boolean"
25
- self.paranoid_configuration.merge!(options) # user options
26
-
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]
25
+ self.paranoid_configuration = {
26
+ column: "deleted_at",
27
+ column_type: "time",
28
+ recover_dependent_associations: true,
29
+ dependent_recovery_window: 2.minutes,
30
+ recovery_value: nil,
31
+ double_tap_destroys_fully: true
32
+ }
33
+ if options[:column_type] == "string"
34
+ paranoid_configuration.merge!(deleted_value: "deleted")
35
+ end
36
+ paranoid_configuration.merge!(allow_nulls: true) if options[:column_type] == "boolean"
37
+ paranoid_configuration.merge!(options) # user options
28
38
 
29
- def self.paranoid_column_reference
30
- "#{self.table_name}.#{paranoid_configuration[:column]}"
39
+ unless %w[time boolean string].include? paranoid_configuration[:column_type]
40
+ raise ArgumentError, "'time', 'boolean' or 'string' expected" \
41
+ " for :column_type option, got #{paranoid_configuration[:column_type]}"
31
42
  end
32
43
 
33
44
  return if paranoid?
@@ -37,28 +48,18 @@ module ActsAsParanoid
37
48
  # Magic!
38
49
  default_scope { where(paranoid_default_scope) }
39
50
 
40
- if paranoid_configuration[:column_type] == 'time'
41
- scope :deleted_inside_time_window, lambda {|time, window|
42
- deleted_after_time((time - window)).deleted_before_time((time + window))
43
- }
44
-
45
- scope :deleted_after_time, lambda { |time| where("#{self.table_name}.#{paranoid_column} > ?", time) }
46
- scope :deleted_before_time, lambda { |time| where("#{self.table_name}.#{paranoid_column} < ?", time) }
47
- end
51
+ define_deleted_time_scopes if paranoid_column_type == :time
48
52
  end
49
53
  end
50
54
 
51
55
  # Extend ActiveRecord's functionality
52
- ActiveRecord::Base.send :extend, ActsAsParanoid
56
+ ActiveRecord::Base.extend ActsAsParanoid
53
57
 
54
58
  # Extend ActiveRecord::Base with paranoid associations
55
- ActiveRecord::Base.send :include, ActsAsParanoid::Associations
59
+ ActiveRecord::Base.include ActsAsParanoid::Associations
56
60
 
57
61
  # Override ActiveRecord::Relation's behavior
58
- ActiveRecord::Relation.send :include, ActsAsParanoid::Relation
62
+ ActiveRecord::Relation.include ActsAsParanoid::Relation
59
63
 
60
64
  # Push the recover callback onto the activerecord callback list
61
65
  ActiveRecord::Callbacks::CALLBACKS.push(:before_recover, :after_recover)
62
-
63
- # Use with_deleted in preloader build_scope
64
- ActiveRecord::Associations::Preloader::Association.send :include, ActsAsParanoid::PreloaderAssociation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsParanoid
2
4
  module Associations
3
5
  def self.included(base)
@@ -16,29 +18,31 @@ module ActsAsParanoid
16
18
  end
17
19
 
18
20
  with_deleted = options.delete(:with_deleted)
19
- result = belongs_to_without_deleted(target, scope, options)
20
-
21
21
  if with_deleted
22
- if result.is_a? Hash
23
- result.values.last.options[:with_deleted] = with_deleted
22
+ if scope
23
+ old_scope = scope
24
+ scope = proc do |*args|
25
+ if old_scope.arity == 0
26
+ instance_exec(&old_scope).with_deleted
27
+ else
28
+ old_scope.call(*args).with_deleted
29
+ end
30
+ end
24
31
  else
25
- result.options[:with_deleted] = with_deleted
26
- end
27
-
28
- unless method_defined? "#{target}_with_unscoped"
29
- class_eval <<-RUBY, __FILE__, __LINE__
30
- def #{target}_with_unscoped(*args)
31
- association = association(:#{target})
32
- return nil if association.options[:polymorphic] && association.klass.nil?
33
- return #{target}_without_unscoped(*args) unless association.klass.paranoid?
34
- association.klass.with_deleted.scoping { association.klass.unscoped { #{target}_without_unscoped(*args) } }
32
+ scope = proc do
33
+ if respond_to? :with_deleted
34
+ self.with_deleted
35
+ else
36
+ all
35
37
  end
36
- alias_method :#{target}_without_unscoped, :#{target}
37
- alias_method :#{target}, :#{target}_with_unscoped
38
- RUBY
38
+ end
39
39
  end
40
40
  end
41
41
 
42
+ result = belongs_to_without_deleted(target, scope, **options)
43
+
44
+ result.values.last.options[:with_deleted] = with_deleted if with_deleted
45
+
42
46
  result
43
47
  end
44
48
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsParanoid
2
4
  module Core
3
5
  def self.included(base)
@@ -23,7 +25,8 @@ module ActsAsParanoid
23
25
 
24
26
  def only_deleted
25
27
  if string_type_with_deleted_value?
26
- without_paranoid_default_scope.where(paranoid_column_reference => paranoid_configuration[:deleted_value])
28
+ without_paranoid_default_scope
29
+ .where(paranoid_column_reference => paranoid_configuration[:deleted_value])
27
30
  elsif boolean_type_not_nullable?
28
31
  without_paranoid_default_scope.where(paranoid_column_reference => true)
29
32
  else
@@ -36,17 +39,18 @@ module ActsAsParanoid
36
39
  end
37
40
 
38
41
  def delete_all(conditions = nil)
39
- where(conditions).update_all(["#{paranoid_configuration[:column]} = ?", delete_now_value])
42
+ where(conditions)
43
+ .update_all(["#{paranoid_configuration[:column]} = ?", delete_now_value])
40
44
  end
41
45
 
42
46
  def paranoid_default_scope
43
47
  if string_type_with_deleted_value?
44
- self.all.table[paranoid_column].eq(nil).
45
- or(self.all.table[paranoid_column].not_eq(paranoid_configuration[:deleted_value]))
48
+ all.table[paranoid_column].eq(nil)
49
+ .or(all.table[paranoid_column].not_eq(paranoid_configuration[:deleted_value]))
46
50
  elsif boolean_type_not_nullable?
47
- self.all.table[paranoid_column].eq(false)
51
+ all.table[paranoid_column].eq(false)
48
52
  else
49
- self.all.table[paranoid_column].eq(nil)
53
+ all.table[paranoid_column].eq(nil)
50
54
  end
51
55
  end
52
56
 
@@ -66,8 +70,14 @@ module ActsAsParanoid
66
70
  paranoid_configuration[:column_type].to_sym
67
71
  end
68
72
 
73
+ def paranoid_column_reference
74
+ "#{table_name}.#{paranoid_column}"
75
+ end
76
+
69
77
  def dependent_associations
70
- self.reflect_on_all_associations.select {|a| [:destroy, :delete_all].include?(a.options[:dependent]) }
78
+ reflect_on_all_associations.select do |a|
79
+ [:destroy, :delete_all].include?(a.options[:dependent])
80
+ end
71
81
  end
72
82
 
73
83
  def delete_now_value
@@ -78,20 +88,27 @@ module ActsAsParanoid
78
88
  end
79
89
  end
80
90
 
81
- protected
91
+ protected
92
+
93
+ def define_deleted_time_scopes
94
+ scope :deleted_inside_time_window, lambda { |time, window|
95
+ deleted_after_time((time - window)).deleted_before_time((time + window))
96
+ }
97
+
98
+ scope :deleted_after_time, lambda { |time|
99
+ where("#{table_name}.#{paranoid_column} > ?", time)
100
+ }
101
+ scope :deleted_before_time, lambda { |time|
102
+ where("#{table_name}.#{paranoid_column} < ?", time)
103
+ }
104
+ end
82
105
 
83
106
  def without_paranoid_default_scope
84
- scope = self.all
107
+ scope = all
85
108
 
86
- if ActiveRecord::VERSION::MAJOR < 5
87
- # ActiveRecord 4.0.*
88
- scope = scope.with_default_scope if ActiveRecord::VERSION::MINOR < 1
89
- scope.where_values.delete(paranoid_default_scope)
90
- else
91
- scope = scope.unscope(where: paranoid_default_scope)
92
- # Fix problems with unscope group chain
93
- scope = scope.unscoped if scope.to_sql.include? paranoid_default_scope.to_sql
94
- end
109
+ scope = scope.unscope(where: paranoid_column)
110
+ # Fix problems with unscope group chain
111
+ scope = scope.unscoped if scope.to_sql.include? paranoid_default_scope.to_sql
95
112
 
96
113
  scope
97
114
  end
@@ -102,7 +119,7 @@ module ActsAsParanoid
102
119
  end
103
120
 
104
121
  def paranoid_value
105
- self.send(self.class.paranoid_column)
122
+ send(self.class.paranoid_column)
106
123
  end
107
124
 
108
125
  # Straight from ActiveRecord 5.1!
@@ -118,9 +135,10 @@ module ActsAsParanoid
118
135
  destroy_dependent_associations!
119
136
 
120
137
  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
-
138
+ # Handle composite keys, otherwise we would just use
139
+ # `self.class.primary_key.to_sym => self.id`.
140
+ self.class
141
+ .delete_all!(Hash[[Array(self.class.primary_key), Array(id)].transpose])
124
142
  decrement_counters_on_associations
125
143
  end
126
144
 
@@ -135,11 +153,11 @@ module ActsAsParanoid
135
153
  if !deleted?
136
154
  with_transaction_returning_status do
137
155
  run_callbacks :destroy do
138
-
139
156
  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
-
157
+ # Handle composite keys, otherwise we would just use
158
+ # `self.class.primary_key.to_sym => self.id`.
159
+ self.class
160
+ .delete_all(Hash[[Array(self.class.primary_key), Array(id)].transpose])
143
161
  decrement_counters_on_associations
144
162
  end
145
163
 
@@ -149,38 +167,39 @@ module ActsAsParanoid
149
167
  self
150
168
  end
151
169
  end
152
- else
153
- if paranoid_configuration[:double_tap_destroys_fully]
154
- destroy_fully!
155
- end
170
+ elsif paranoid_configuration[:double_tap_destroys_fully]
171
+ destroy_fully!
156
172
  end
157
173
  end
158
174
 
159
- alias_method :destroy, :destroy!
175
+ alias destroy destroy!
176
+
177
+ def recover(options = {})
178
+ return if !deleted?
160
179
 
161
- def recover(options={})
162
- return if !self.deleted?
163
180
  options = {
164
- :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
165
- :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window],
166
- :raise_error => false
181
+ recursive: self.class.paranoid_configuration[:recover_dependent_associations],
182
+ recovery_window: self.class.paranoid_configuration[:dependent_recovery_window],
183
+ raise_error: false
167
184
  }.merge(options)
168
185
 
169
186
  self.class.transaction do
170
187
  run_callbacks :recover do
171
- recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
188
+ if options[:recursive]
189
+ recover_dependent_associations(options[:recovery_window], options)
190
+ end
172
191
  increment_counters_on_associations
173
192
  self.paranoid_value = self.class.paranoid_configuration[:recovery_value]
174
193
  if options[:raise_error]
175
- self.save!
194
+ save!
176
195
  else
177
- self.save
196
+ save
178
197
  end
179
198
  end
180
199
  end
181
200
  end
182
201
 
183
- def recover!(options={})
202
+ def recover!(options = {})
184
203
  options[:raise_error] = true
185
204
 
186
205
  recover(options)
@@ -190,14 +209,7 @@ module ActsAsParanoid
190
209
  self.class.dependent_associations.each do |reflection|
191
210
  next unless (klass = get_reflection_class(reflection)).paranoid?
192
211
 
193
- scope = klass.only_deleted
194
-
195
- # Merge in the association's 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
212
+ scope = klass.only_deleted.merge(get_association_scope(reflection: reflection))
201
213
 
202
214
  # We can only recover by window if both parent and dependant have a
203
215
  # paranoid column type of :time.
@@ -215,77 +227,75 @@ module ActsAsParanoid
215
227
  self.class.dependent_associations.each do |reflection|
216
228
  next unless (klass = get_reflection_class(reflection)).paranoid?
217
229
 
218
- scope = klass.only_deleted
219
-
220
- # Merge in the association's 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
226
-
227
- scope.each do |object|
228
- object.destroy!
229
- end
230
+ klass
231
+ .only_deleted.merge(get_association_scope(reflection: reflection))
232
+ .each(&:destroy!)
230
233
  end
231
234
  end
232
235
 
233
236
  def deleted?
234
- @destroyed || !if self.class.string_type_with_deleted_value?
235
- paranoid_value != self.class.delete_now_value || paranoid_value.nil?
237
+ return true if @destroyed
238
+
239
+ if self.class.string_type_with_deleted_value?
240
+ paranoid_value == paranoid_configuration[:deleted_value]
236
241
  elsif self.class.boolean_type_not_nullable?
237
- paranoid_value == false
242
+ paranoid_value == true
238
243
  else
239
- paranoid_value.nil?
244
+ !paranoid_value.nil?
240
245
  end
241
246
  end
242
247
 
243
- alias_method :destroyed?, :deleted?
248
+ alias destroyed? deleted?
244
249
 
245
250
  def deleted_fully?
246
251
  @destroyed
247
252
  end
248
253
 
249
- alias_method :destroyed_fully?, :deleted_fully?
254
+ alias destroyed_fully? deleted_fully?
250
255
 
251
256
  private
252
257
 
258
+ def get_association_scope(reflection:)
259
+ ActiveRecord::Associations::AssociationScope.scope(association(reflection.name))
260
+ end
261
+
253
262
  def get_reflection_class(reflection)
254
263
  if reflection.macro == :belongs_to && reflection.options.include?(:polymorphic)
255
- self.send(reflection.foreign_type).constantize
264
+ send(reflection.foreign_type).constantize
256
265
  else
257
266
  reflection.klass
258
267
  end
259
268
  end
260
269
 
261
270
  def paranoid_value=(value)
262
- self.send("#{self.class.paranoid_column}=", value)
271
+ write_attribute(self.class.paranoid_column, value)
263
272
  end
264
273
 
265
- def update_counters_on_associations method_sym
266
-
274
+ def update_counters_on_associations(method_sym)
267
275
  return unless [:decrement_counter, :increment_counter].include? method_sym
268
276
 
269
277
  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
278
+ associated_object = send(assoc_reflection.name)
279
+ next unless associated_object
280
+
281
+ counter_cache_column = assoc_reflection.counter_cache_column
282
+ associated_object.class.send(method_sym, counter_cache_column,
283
+ associated_object.id)
274
284
  end
275
285
  end
276
286
 
277
287
  def each_counter_cached_association_reflection
278
- _reflections.each do |name, reflection|
288
+ _reflections.each do |_name, reflection|
279
289
  yield reflection if reflection.belongs_to? && reflection.counter_cache_column
280
290
  end
281
291
  end
282
292
 
283
293
  def increment_counters_on_associations
284
- update_counters_on_associations :increment_counter
294
+ update_counters_on_associations :increment_counter
285
295
  end
286
296
 
287
297
  def decrement_counters_on_associations
288
- update_counters_on_associations :decrement_counter
298
+ update_counters_on_associations :decrement_counter
289
299
  end
290
300
 
291
301
  def stale_paranoid_value