acts_as_paranoid 0.6.3 → 0.7.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.
@@ -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!([Array(self.class.primary_key), Array(id)].transpose.to_h)
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([Array(self.class.primary_key), Array(id)].transpose.to_h)
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,77 @@ 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
274
+ def update_counters_on_associations(method_sym)
275
+ each_counter_cached_association_reflection do |assoc_reflection|
276
+ reflection_options = assoc_reflection.options
277
+ next unless reflection_options[:counter_cache]
266
278
 
267
- return unless [:decrement_counter, :increment_counter].include? method_sym
279
+ associated_object = send(assoc_reflection.name)
280
+ next unless associated_object
268
281
 
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
282
+ counter_cache_column = assoc_reflection.counter_cache_column
283
+ associated_object.class.send(method_sym, counter_cache_column,
284
+ associated_object.id)
285
+ associated_object.touch if reflection_options[:touch]
274
286
  end
275
287
  end
276
288
 
277
289
  def each_counter_cached_association_reflection
278
- _reflections.each do |name, reflection|
290
+ _reflections.each do |_name, reflection|
279
291
  yield reflection if reflection.belongs_to? && reflection.counter_cache_column
280
292
  end
281
293
  end
282
294
 
283
295
  def increment_counters_on_associations
284
- update_counters_on_associations :increment_counter
296
+ update_counters_on_associations :increment_counter
285
297
  end
286
298
 
287
299
  def decrement_counters_on_associations
288
- update_counters_on_associations :decrement_counter
300
+ update_counters_on_associations :decrement_counter
289
301
  end
290
302
 
291
303
  def stale_paranoid_value
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsParanoid
2
4
  module Relation
3
5
  def self.included(base)
@@ -1,4 +1,6 @@
1
- require 'active_support/core_ext/array/wrap'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/wrap"
2
4
 
3
5
  module ActsAsParanoid
4
6
  module Validations
@@ -6,85 +8,17 @@ module ActsAsParanoid
6
8
  base.extend ClassMethods
7
9
  end
8
10
 
9
- class UniquenessWithoutDeletedValidator
10
- def self.[](version)
11
- name = "V#{version.to_s.tr('.', '_')}"
12
- unless constants.include? name.to_sym
13
- raise "Unknown validator version #{name.inspect}; expected one of #{constants.sort.join(', ')}"
14
- end
15
- const_get name
16
- end
17
-
18
- class V5 < ActiveRecord::Validations::UniquenessValidator
19
- def validate_each(record, attribute, value)
20
- finder_class = find_finder_class_for(record)
21
- table = finder_class.arel_table
22
-
23
- relation = build_relation(finder_class, attribute, value)
24
- [Array(finder_class.primary_key), Array(record.send(:id))].transpose.each do |pk_key, pk_value|
25
- relation = relation.where(table[pk_key.to_sym].not_eq(pk_value))
26
- end if record.persisted?
27
-
28
- Array.wrap(options[:scope]).each do |scope_item|
29
- relation = relation.where(table[scope_item].eq(record.public_send(scope_item)))
30
- end
31
-
32
- if relation.where(finder_class.paranoid_default_scope).exists?(relation)
33
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
34
- end
35
- end
36
-
37
- protected
38
-
39
- def build_relation(klass, attribute, value)
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
45
- end
46
- end
47
-
48
- class V4 < ActiveRecord::Validations::UniquenessValidator
49
- def validate_each(record, attribute, value)
50
- finder_class = find_finder_class_for(record)
51
- table = finder_class.arel_table
52
-
53
- # TODO: Use record.class.column_types[attribute.to_s].coder ?
54
- coder = record.class.column_types[attribute.to_s]
55
-
56
- if value && coder
57
- value = if coder.respond_to? :type_cast_for_database
58
- coder.type_cast_for_database value
59
- else
60
- coder.type_cast_for_write value
61
- end
62
- end
63
-
64
- relation = build_relation(finder_class, table, attribute, value)
65
- [Array(finder_class.primary_key), Array(record.send(:id))].transpose.each do |pk_key, pk_value|
66
- relation = relation.and(table[pk_key.to_sym].not_eq(pk_value))
67
- end if record.persisted?
68
-
69
- Array.wrap(options[:scope]).each do |scope_item|
70
- scope_value = record.send(scope_item)
71
- relation = relation.and(table[scope_item].eq(scope_value))
72
- end
73
-
74
- # Re-add ActsAsParanoid default scope conditions manually.
75
- if finder_class.unscoped.where(finder_class.paranoid_default_scope).where(relation).exists?
76
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
77
- end
78
- end
79
- end
11
+ class UniquenessWithoutDeletedValidator < ActiveRecord::Validations::UniquenessValidator
12
+ private
80
13
 
81
- class V6 < V5
14
+ def build_relation(klass, attribute, value)
15
+ super.where(klass.paranoid_default_scope)
82
16
  end
83
17
  end
84
18
 
85
19
  module ClassMethods
86
20
  def validates_uniqueness_of_without_deleted(*attr_names)
87
- validates_with UniquenessWithoutDeletedValidator[ActiveRecord::VERSION::MAJOR], _merge_attributes(attr_names)
21
+ validates_with UniquenessWithoutDeletedValidator, _merge_attributes(attr_names)
88
22
  end
89
23
  end
90
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActsAsParanoid
2
- VERSION = "0.6.3"
4
+ VERSION = "0.7.1"
3
5
  end
@@ -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