acts_as_paranoid 0.4.1 → 0.4.5

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 57c69c6d7676bda21b0f8da016bd556f77003894
4
+ data.tar.gz: 5293e8a3b417fecdfe117aefddcd8e2a0d2f4b09
5
+ SHA512:
6
+ metadata.gz: 8ae49e59c2f487c80243bb42c4d6e33c4f1676ecd0d039ba420c1e1e49e02b83a4c6635515d1ef53dd4ba0c68ebce427b386ce684992d4139ef2e4c638f61dd5
7
+ data.tar.gz: f449a10feae541515688ed6c55446aa815025982f293a2375c44ec74b68a8cfe7928abc1dffee3758a868bca4d6d92eb13632642bb0dc40279d47e5973a7bfb0
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Gonçalo Silva
1
+ Copyright (c) 2014 Zachary Scott, Gonçalo Silva, Rick Olson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,15 +1,11 @@
1
1
  # ActsAsParanoid
2
2
 
3
+ [![Build Status](https://travis-ci.org/ActsAsParanoid/acts_as_paranoid.png?branch=0-4-stable)](https://travis-ci.org/ActsAsParanoid/acts_as_paranoid)
4
+
3
5
  A simple plugin which hides records instead of deleting them, being able to recover them.
4
6
 
5
7
  **This branch targets Rails 3.2.** If you're working with another version, switch to the corresponding branch.
6
8
 
7
- ## Credits
8
-
9
- This plugin was inspired by [acts_as_paranoid](http://github.com/technoweenie/acts_as_paranoid) and [acts_as_active](http://github.com/fernandoluizao/acts_as_active).
10
-
11
- While porting it to Rails 3, I decided to apply the ideas behind those plugins to an unified solution while removing a **lot** of the complexity found in them. I eventually ended up writing a new plugin from scratch.
12
-
13
9
  ## Usage
14
10
 
15
11
  You can enable ActsAsParanoid like this:
@@ -186,29 +182,34 @@ Paranoiac.pretty.only_deleted.count #=> 1
186
182
  Associations are also supported. From the simplest behaviors you'd expect to more nifty things like the ones mentioned previously or the usage of the `:with_deleted` option with `belongs_to`
187
183
 
188
184
  ```ruby
189
- class ParanoiacParent < ActiveRecord::Base
190
- has_many :children, :class_name => "ParanoiacChild"
185
+ class Parent < ActiveRecord::Base
186
+ has_many :children, :class_name => "ParanoiacChild"
191
187
  end
192
188
 
193
189
  class ParanoiacChild < ActiveRecord::Base
194
- belongs_to :parent, :class_name => "ParanoiacParent"
195
- belongs_to :parent_with_deleted, :class_name => "ParanoiacParent", :with_deleted => true
190
+ acts_as_paranoid
191
+ belongs_to :parent
192
+
193
+ # You may need to provide a foreign_key like this
194
+ belongs_to :parent_including_deleted, :class_name => "Parent", foreign_key => 'parent_id', :with_deleted => true
196
195
  end
197
196
 
198
- parent = ParanoiacParent.first
197
+ parent = Parent.first
199
198
  child = parent.children.create
200
199
  parent.destroy
201
200
 
202
201
  child.parent #=> nil
203
- child.parent_with_deleted #=> ParanoiacParent (it works!)
202
+ child.parent_including_deleted #=> Parent (it works!)
204
203
  ```
205
204
 
206
205
  ## Caveats
207
206
 
208
207
  Watch out for these caveats:
209
208
 
209
+
210
210
  - You cannot use scopes named `with_deleted` and `only_deleted`
211
211
  - You cannot use scopes named `deleted_inside_time_window`, `deleted_before_time`, `deleted_after_time` **if** your paranoid column's type is `time`
212
+ - You cannot name association `*_with_deleted`
212
213
  - `unscoped` will return all records, deleted or not
213
214
 
214
215
  # Support
@@ -219,20 +220,21 @@ This gem supports the most recent versions of Rails and Ruby.
219
220
 
220
221
  For Rails 3.2 check the README at the [rails3.2](https://github.com/goncalossilva/rails3_acts_as_paranoid/tree/rails3.2) branch and add this to your Gemfile:
221
222
 
222
- gem "acts_as_paranoid", "~>0.4.0"
223
+ gem "acts_as_paranoid", "~> 0.4.0"
223
224
 
224
225
  For Rails 3.1 check the README at the [rails3.1](https://github.com/goncalossilva/rails3_acts_as_paranoid/tree/rails3.1) branch and add this to your Gemfile:
225
226
 
226
- gem "rails3_acts_as_paranoid", "~>0.1.4"
227
+ gem "rails3_acts_as_paranoid", "~>0.1.4"
227
228
 
228
229
  For Rails 3.0 check the README at the [rails3.0](https://github.com/goncalossilva/rails3_acts_as_paranoid/tree/rails3.0) branch and add this to your Gemfile:
229
230
 
230
- gem "rails3_acts_as_paranoid", "~>0.0.9"
231
+ gem "rails3_acts_as_paranoid", "~>0.0.9"
231
232
 
232
233
 
233
234
  ## Ruby
234
235
 
235
- This gem is tested on Ruby 1.9, JRuby and Rubinius (both in 1.9 mode). It *might* work fine in 1.8, but it's not officially supported.
236
+ This gem is tested on Ruby 1.9, JRuby and Rubinius (both in 1.9 mode).
237
+
236
238
 
237
239
  # Acknowledgements
238
240
 
@@ -244,4 +246,11 @@ This gem is tested on Ruby 1.9, JRuby and Rubinius (both in 1.9 mode). It *might
244
246
  * To [Craig Walker](https://github.com/softcraft-development) for Rails 3.1 support and fixing various pending issues
245
247
  * To [Charles G.](https://github.com/chuckg) for Rails 3.2 support and for making a desperately needed global code refactoring
246
248
 
247
- Copyright © 2010 Gonçalo Silva, released under the MIT license
249
+ ## Credits
250
+
251
+ This plugin was inspired by [acts_as_paranoid](http://github.com/technoweenie/acts_as_paranoid) and [acts_as_active](http://github.com/fernandoluizao/acts_as_active).
252
+
253
+ While porting it to Rails 3, I decided to apply the ideas behind those plugins to an unified solution while removing a **lot** of the complexity found in them. I eventually ended up writing a new plugin from scratch.
254
+
255
+
256
+ Copyright © 2014 Zachary Scott, Gonçalo Silva, Rick Olson, released under the MIT license
@@ -94,7 +94,7 @@ module ActsAsParanoid
94
94
  run_callbacks :destroy do
95
95
  destroy_dependent_associations!
96
96
  # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
97
- self.class.delete_all!(Hash[[Array(self.class.primary_key), Array(self.id)].transpose])
97
+ self.class.delete_all!(Hash[[Array(self.class.primary_key), Array(self.id)].transpose]) if persisted?
98
98
  self.paranoid_value = self.class.delete_now_value
99
99
  freeze
100
100
  end
@@ -106,7 +106,7 @@ module ActsAsParanoid
106
106
  with_transaction_returning_status do
107
107
  run_callbacks :destroy do
108
108
  # Handle composite keys, otherwise we would just use `self.class.primary_key.to_sym => self.id`.
109
- self.class.delete_all(Hash[[Array(self.class.primary_key), Array(self.id)].transpose])
109
+ self.class.delete_all(Hash[[Array(self.class.primary_key), Array(self.id)].transpose]) if persisted?
110
110
  self.paranoid_value = self.class.delete_now_value
111
111
  self
112
112
  end
@@ -134,17 +134,17 @@ module ActsAsParanoid
134
134
 
135
135
  def recover_dependent_associations(window, options)
136
136
  self.class.dependent_associations.each do |reflection|
137
- next unless reflection.klass.paranoid?
137
+ next unless (klass = get_reflection_class(reflection)).paranoid?
138
138
 
139
- scope = reflection.klass.only_deleted
139
+ scope = klass.only_deleted
140
140
 
141
141
  # Merge in the association's scope
142
142
  scope = scope.merge(association(reflection.name).association_scope)
143
143
 
144
144
  # We can only recover by window if both parent and dependant have a
145
145
  # paranoid column type of :time.
146
- if self.class.paranoid_column_type == :time && reflection.klass.paranoid_column_type == :time
147
- scope = scope.merge(reflection.klass.deleted_inside_time_window(paranoid_value, window))
146
+ if self.class.paranoid_column_type == :time && klass.paranoid_column_type == :time
147
+ scope = scope.merge(klass.deleted_inside_time_window(paranoid_value, window))
148
148
  end
149
149
 
150
150
  scope.each do |object|
@@ -155,9 +155,9 @@ module ActsAsParanoid
155
155
 
156
156
  def destroy_dependent_associations!
157
157
  self.class.dependent_associations.each do |reflection|
158
- next unless reflection.klass.paranoid?
158
+ next unless (klass = get_reflection_class(reflection)).paranoid?
159
159
 
160
- scope = reflection.klass.only_deleted
160
+ scope = klass.only_deleted
161
161
 
162
162
  # Merge in the association's scope
163
163
  scope = scope.merge(association(reflection.name).association_scope)
@@ -177,6 +177,14 @@ module ActsAsParanoid
177
177
 
178
178
  private
179
179
 
180
+ def get_reflection_class(reflection)
181
+ if reflection.macro == :belongs_to && reflection.options.include?(:polymorphic)
182
+ self.send(reflection.foreign_type).constantize
183
+ else
184
+ reflection.klass
185
+ end
186
+ end
187
+
180
188
  def paranoid_value=(value)
181
189
  self.send("#{self.class.paranoid_column}=", value)
182
190
  end
@@ -5,7 +5,7 @@ module ActsAsParanoid
5
5
  def paranoid?
6
6
  klass.try(:paranoid?) ? true : false
7
7
  end
8
-
8
+
9
9
  def paranoid_deletion_attributes
10
10
  { klass.paranoid_column => klass.delete_now_value }
11
11
  end
@@ -18,7 +18,7 @@ module ActsAsParanoid
18
18
  orig_delete_all
19
19
  end
20
20
  end
21
-
21
+
22
22
  def delete_all(conditions = nil)
23
23
  if paranoid?
24
24
  update_all(paranoid_deletion_attributes, conditions)
@@ -0,0 +1,3 @@
1
+ module ActsAsParanoid
2
+ VERSION = "0.4.5"
3
+ end
@@ -1,27 +1,23 @@
1
- require 'active_record/base'
2
- require 'active_record/relation'
3
- require 'active_record/callbacks'
4
1
  require 'acts_as_paranoid/core'
5
2
  require 'acts_as_paranoid/associations'
6
3
  require 'acts_as_paranoid/validations'
7
4
  require 'acts_as_paranoid/relation'
8
5
 
9
-
10
6
  module ActsAsParanoid
11
-
7
+
12
8
  def paranoid?
13
9
  self.included_modules.include?(ActsAsParanoid::Core)
14
10
  end
15
-
11
+
16
12
  def validates_as_paranoid
17
13
  include ActsAsParanoid::Validations
18
14
  end
19
-
15
+
20
16
  def acts_as_paranoid(options = {})
21
17
  raise ArgumentError, "Hash expected, got #{options.class.name}" if not options.is_a?(Hash) and not options.empty?
22
-
18
+
23
19
  class_attribute :paranoid_configuration, :paranoid_column_reference
24
-
20
+
25
21
  self.paranoid_configuration = { :column => "deleted_at", :column_type => "time", :recover_dependent_associations => true, :dependent_recovery_window => 2.minutes }
26
22
  self.paranoid_configuration.merge!({ :deleted_value => "deleted" }) if options[:column_type] == "string"
27
23
  self.paranoid_configuration.merge!(options) # user options
@@ -29,11 +25,11 @@ module ActsAsParanoid
29
25
  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]
30
26
 
31
27
  self.paranoid_column_reference = "#{self.table_name}.#{paranoid_configuration[:column]}"
32
-
28
+
33
29
  return if paranoid?
34
-
30
+
35
31
  include ActsAsParanoid::Core
36
-
32
+
37
33
  # Magic!
38
34
  default_scope { where(paranoid_default_scope_sql) }
39
35
 
@@ -42,8 +38,8 @@ module ActsAsParanoid
42
38
  deleted_after_time((time - window)).deleted_before_time((time + window))
43
39
  }
44
40
 
45
- scope :deleted_after_time, lambda { |time| where("#{paranoid_column} > ?", time) }
46
- scope :deleted_before_time, lambda { |time| where("#{paranoid_column} < ?", time) }
41
+ scope :deleted_after_time, lambda { |time| where("#{self.table_name}.#{paranoid_column} > ?", time) }
42
+ scope :deleted_before_time, lambda { |time| where("#{self.table_name}.#{paranoid_column} < ?", time) }
47
43
  end
48
44
  end
49
45
  end
@@ -0,0 +1,222 @@
1
+ require 'test_helper'
2
+
3
+ class AssociationsTest < ParanoidBaseTest
4
+ def test_removal_with_associations
5
+ # This test shows that the current implementation doesn't handle
6
+ # assciation deletion correctly (when hard deleting via parent-object)
7
+ paranoid_company_1 = ParanoidDestroyCompany.create! :name => "ParanoidDestroyCompany #1"
8
+ paranoid_company_2 = ParanoidDeleteCompany.create! :name => "ParanoidDestroyCompany #1"
9
+ paranoid_company_1.paranoid_products.create! :name => "ParanoidProduct #1"
10
+ paranoid_company_2.paranoid_products.create! :name => "ParanoidProduct #2"
11
+
12
+ assert_equal 1, ParanoidDestroyCompany.count
13
+ assert_equal 1, ParanoidDeleteCompany.count
14
+ assert_equal 2, ParanoidProduct.count
15
+
16
+ ParanoidDestroyCompany.first.destroy
17
+ assert_equal 0, ParanoidDestroyCompany.count
18
+ assert_equal 1, ParanoidProduct.count
19
+ assert_equal 1, ParanoidDestroyCompany.with_deleted.count
20
+ assert_equal 2, ParanoidProduct.with_deleted.count
21
+
22
+ ParanoidDestroyCompany.with_deleted.first.destroy!
23
+ assert_equal 0, ParanoidDestroyCompany.count
24
+ assert_equal 1, ParanoidProduct.count
25
+ assert_equal 0, ParanoidDestroyCompany.with_deleted.count
26
+ assert_equal 1, ParanoidProduct.with_deleted.count
27
+
28
+ ParanoidDeleteCompany.with_deleted.first.destroy!
29
+ assert_equal 0, ParanoidDeleteCompany.count
30
+ assert_equal 0, ParanoidProduct.count
31
+ assert_equal 0, ParanoidDeleteCompany.with_deleted.count
32
+ assert_equal 0, ParanoidProduct.with_deleted.count
33
+ end
34
+
35
+ def test_belongs_to_with_deleted
36
+ paranoid_time = ParanoidTime.first
37
+ paranoid_has_many_dependant = paranoid_time.paranoid_has_many_dependants.create(:name => 'dependant!')
38
+
39
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time
40
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_deleted
41
+
42
+ paranoid_time.destroy
43
+
44
+ assert_nil paranoid_has_many_dependant.paranoid_time(true)
45
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_with_deleted(true)
46
+ end
47
+
48
+ def test_belongs_to_polymorphic_with_deleted
49
+ paranoid_time = ParanoidTime.first
50
+ paranoid_has_many_dependant = ParanoidHasManyDependant.create!(:name => 'dependant!', :paranoid_time_polymorphic_with_deleted => paranoid_time)
51
+
52
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time
53
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_polymorphic_with_deleted
54
+
55
+ paranoid_time.destroy
56
+
57
+ assert_nil paranoid_has_many_dependant.paranoid_time(true)
58
+ assert_equal paranoid_time, paranoid_has_many_dependant.paranoid_time_polymorphic_with_deleted(true)
59
+ end
60
+
61
+ def test_belongs_to_nil_polymorphic_with_deleted
62
+ paranoid_time = ParanoidTime.first
63
+ paranoid_has_many_dependant = ParanoidHasManyDependant.create!(:name => 'dependant!', :paranoid_time_polymorphic_with_deleted => nil)
64
+
65
+ assert_nil paranoid_has_many_dependant.paranoid_time
66
+ assert_nil paranoid_has_many_dependant.paranoid_time_polymorphic_with_deleted
67
+
68
+ paranoid_time.destroy
69
+
70
+ assert_nil paranoid_has_many_dependant.paranoid_time(true)
71
+ assert_nil paranoid_has_many_dependant.paranoid_time_polymorphic_with_deleted(true)
72
+ end
73
+
74
+ def test_belongs_to_options
75
+ paranoid_time = ParanoidHasManyDependant.reflections[:paranoid_time]
76
+ assert_equal :belongs_to, paranoid_time.macro
77
+ assert_nil paranoid_time.options[:with_deleted]
78
+ end
79
+
80
+ def test_belongs_to_with_deleted_options
81
+ paranoid_time_with_deleted = ParanoidHasManyDependant.reflections[:paranoid_time_with_deleted]
82
+ assert_equal :belongs_to, paranoid_time_with_deleted.macro
83
+ assert paranoid_time_with_deleted.options[:with_deleted]
84
+ end
85
+
86
+ def test_belongs_to_polymorphic_with_deleted_options
87
+ paranoid_time_polymorphic_with_deleted = ParanoidHasManyDependant.reflections[:paranoid_time_polymorphic_with_deleted]
88
+ assert_equal :belongs_to, paranoid_time_polymorphic_with_deleted.macro
89
+ assert paranoid_time_polymorphic_with_deleted.options[:with_deleted]
90
+ end
91
+
92
+ def test_only_find_associated_records_when_finding_with_paranoid_deleted
93
+ parent = ParanoidBelongsDependant.create
94
+ child = ParanoidHasManyDependant.create
95
+ parent.paranoid_has_many_dependants << child
96
+
97
+ unrelated_parent = ParanoidBelongsDependant.create
98
+ unrelated_child = ParanoidHasManyDependant.create
99
+ unrelated_parent.paranoid_has_many_dependants << unrelated_child
100
+
101
+ child.destroy
102
+ assert_paranoid_deletion(child)
103
+
104
+ assert_equal [child], parent.paranoid_has_many_dependants.with_deleted.to_a
105
+ end
106
+
107
+ def test_cannot_find_a_paranoid_deleted_many_many_association
108
+ left = ParanoidManyManyParentLeft.create
109
+ right = ParanoidManyManyParentRight.create
110
+ left.paranoid_many_many_parent_rights << right
111
+
112
+ left.paranoid_many_many_parent_rights.delete(right)
113
+
114
+ left.reload
115
+
116
+ assert_equal [], left.paranoid_many_many_children, "Linking objects not deleted"
117
+ assert_equal [], left.paranoid_many_many_parent_rights, "Associated objects not unlinked"
118
+ assert_equal right, ParanoidManyManyParentRight.find(right.id), "Associated object deleted"
119
+ end
120
+
121
+ def test_cannot_find_a_paranoid_destroyed_many_many_association
122
+ left = ParanoidManyManyParentLeft.create
123
+ right = ParanoidManyManyParentRight.create
124
+ left.paranoid_many_many_parent_rights << right
125
+
126
+ left.paranoid_many_many_parent_rights.destroy(right)
127
+
128
+ left.reload
129
+
130
+ assert_equal [], left.paranoid_many_many_children, "Linking objects not deleted"
131
+ assert_equal [], left.paranoid_many_many_parent_rights, "Associated objects not unlinked"
132
+ assert_equal right, ParanoidManyManyParentRight.find(right.id), "Associated object deleted"
133
+ end
134
+
135
+ def test_cannot_find_a_has_many_through_object_when_its_linking_object_is_paranoid_destroyed
136
+ left = ParanoidManyManyParentLeft.create
137
+ right = ParanoidManyManyParentRight.create
138
+ left.paranoid_many_many_parent_rights << right
139
+
140
+ child = left.paranoid_many_many_children.first
141
+
142
+ child.destroy
143
+
144
+ left.reload
145
+
146
+ assert_equal [], left.paranoid_many_many_parent_rights, "Associated objects not deleted"
147
+ end
148
+
149
+ def test_cannot_find_a_paranoid_deleted_model
150
+ model = ParanoidBelongsDependant.create
151
+ model.destroy
152
+
153
+ assert_raises ActiveRecord::RecordNotFound do
154
+ ParanoidBelongsDependant.find(model.id)
155
+ end
156
+ end
157
+
158
+ def test_bidirectional_has_many_through_association_clear_is_paranoid
159
+ left = ParanoidManyManyParentLeft.create
160
+ right = ParanoidManyManyParentRight.create
161
+ left.paranoid_many_many_parent_rights << right
162
+
163
+ child = left.paranoid_many_many_children.first
164
+ assert_equal left, child.paranoid_many_many_parent_left, "Child's left parent is incorrect"
165
+ assert_equal right, child.paranoid_many_many_parent_right, "Child's right parent is incorrect"
166
+
167
+ left.paranoid_many_many_parent_rights.clear
168
+
169
+ assert_paranoid_deletion(child)
170
+ end
171
+
172
+ def test_bidirectional_has_many_through_association_destroy_is_paranoid
173
+ left = ParanoidManyManyParentLeft.create
174
+ right = ParanoidManyManyParentRight.create
175
+ left.paranoid_many_many_parent_rights << right
176
+
177
+ child = left.paranoid_many_many_children.first
178
+ assert_equal left, child.paranoid_many_many_parent_left, "Child's left parent is incorrect"
179
+ assert_equal right, child.paranoid_many_many_parent_right, "Child's right parent is incorrect"
180
+
181
+ left.paranoid_many_many_parent_rights.destroy(right)
182
+
183
+ assert_paranoid_deletion(child)
184
+ end
185
+
186
+ def test_bidirectional_has_many_through_association_delete_is_paranoid
187
+ left = ParanoidManyManyParentLeft.create
188
+ right = ParanoidManyManyParentRight.create
189
+ left.paranoid_many_many_parent_rights << right
190
+
191
+ child = left.paranoid_many_many_children.first
192
+ assert_equal left, child.paranoid_many_many_parent_left, "Child's left parent is incorrect"
193
+ assert_equal right, child.paranoid_many_many_parent_right, "Child's right parent is incorrect"
194
+
195
+ left.paranoid_many_many_parent_rights.delete(right)
196
+
197
+ assert_paranoid_deletion(child)
198
+ end
199
+
200
+ def test_belongs_to_on_normal_model_is_paranoid
201
+ not_paranoid = HasOneNotParanoid.create
202
+ not_paranoid.paranoid_time = ParanoidTime.create
203
+
204
+ assert not_paranoid.save
205
+ assert_not_nil not_paranoid.paranoid_time
206
+ end
207
+
208
+ def test_double_belongs_to_with_deleted
209
+ not_paranoid = DoubleHasOneNotParanoid.create
210
+ not_paranoid.paranoid_time = ParanoidTime.create
211
+
212
+ assert not_paranoid.save
213
+ assert_not_nil not_paranoid.paranoid_time
214
+ end
215
+
216
+ def test_mass_assignment_of_paranoid_column_enabled
217
+ now = Time.now
218
+ record = ParanoidTime.create! :name => 'Foo', :deleted_at => now
219
+ assert_equal 'Foo', record.name
220
+ assert_equal now, record.deleted_at
221
+ end
222
+ end