acts_as_paranoid 0.6.3 → 0.7.0

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