acts_as_paranoid 0.4.1 → 0.4.5

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