rails3_acts_as_paranoid 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,103 +14,227 @@ While porting it to Rails 3, I decided to apply the ideas behind those plugins t
14
14
 
15
15
  You can enable ActsAsParanoid like this:
16
16
 
17
- class Paranoiac < ActiveRecord::Base
18
- acts_as_paranoid
19
- end
17
+ ```ruby
18
+ class Paranoiac < ActiveRecord::Base
19
+ acts_as_paranoid
20
+ end
21
+ ```
20
22
 
21
23
  ### Options
22
24
 
23
25
  You can also specify the name of the column to store it's *deletion* and the type of data it holds:
24
26
 
25
- - :column => 'deleted_at'
26
- - :type => 'time'
27
+ - `:column => 'deleted_at'`
28
+ - `:column_type => 'time'`
27
29
 
28
- The values shown are the defaults. While *column* can be anything (as long as it exists in your database), *type* is restricted to "boolean", "time" or "string".
30
+ The values shown are the defaults. While *column* can be anything (as long as it exists in your database), *type* is restricted to:
29
31
 
30
- If your column type is a "string", you can also specify which value to use when marking an object as deleted by passing `:deleted_value` (default is "deleted").
32
+ - `boolean`
33
+ - `time` or
34
+ - `string`
35
+
36
+ If your column type is a `string`, you can also specify which value to use when marking an object as deleted by passing `:deleted_value` (default is "deleted").
31
37
 
32
38
  ### Filtering
33
39
 
34
40
  If a record is deleted by ActsAsParanoid, it won't be retrieved when accessing the database. So, `Paranoiac.all` will **not** include the deleted_records. if you want to access them, you have 2 choices:
35
41
 
36
- Paranoiac.only_deleted # retrieves the deleted records
37
- Paranoiac.with_deleted # retrieves all records, deleted or not
42
+ ```ruby
43
+ Paranoiac.only_deleted # retrieves the deleted records
44
+ Paranoiac.with_deleted # retrieves all records, deleted or not
45
+ ```
46
+
47
+ When using the default `column_type` of `'time'`, the following extra scopes are provided:
48
+
49
+ ```ruby
50
+ time = Time.now
51
+
52
+ Paranoiac.deleted_after_time(time)
53
+ Paranoiac.deleted_before_time(time)
54
+
55
+ # Or roll it all up and get a nice window:
56
+ Paranoiac.deleted_inside_time_window(time, 2.minutes)
57
+ ```
38
58
 
39
59
  ### Real deletion
40
60
 
41
61
  In order to really delete a record, just use:
42
62
 
43
- paranoiac.destroy!
44
- Paranoiac.delete_all!(conditions)
63
+ ```ruby
64
+ paranoiac.destroy!
65
+ Paranoiac.delete_all!(conditions)
66
+ ```
45
67
 
46
- You can also definitively delete a record by calling `destroy` or `delete_all` on it twice. If a record was already deleted (hidden by ActsAsParanoid) and you delete it again, it will be removed from the database. Take this example:
68
+ You can also permanently delete a record by calling `destroy` or `delete_all` on it **twice**. If a record was already deleted (hidden by ActsAsParanoid) and you delete it again, it will be removed from the database. Take this example:
47
69
 
48
- Paranoiac.first.destroy # does NOT delete the first record, just hides it
49
- Paranoiac.only_deleted.destroy # deletes the first record from the database
70
+ ```ruby
71
+ p = Paranoiac.first
72
+ p.destroy # does NOT delete the first record, just hides it
73
+ Paranoiac.only_deleted.where(:id => p.id).destroy # deletes the first record from the database
74
+ ```
50
75
 
51
76
  ### Recovery
52
77
 
53
78
  Recovery is easy. Just invoke `recover` on it, like this:
54
79
 
55
- Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover
80
+ ```ruby
81
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover
82
+ ```
56
83
 
57
84
  All associations marked as `:dependent => :destroy` are also recursively recovered. If you would like to disable this behavior, you can call `recover` with the `recursive` option:
58
85
 
59
- Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recursive => false)
86
+ ```ruby
87
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recursive => false)
88
+ ```
60
89
 
61
- If you would like to change the default behavior for a model, you can use the `recover_dependent_associations` option
90
+ If you would like to change this default behavior for one model, you can use the `recover_dependent_associations` option
62
91
 
63
- class Paranoiac < ActiveRecord::Base
64
- acts_as_paranoid :recover_dependent_associations => false
65
- end
92
+ ```ruby
93
+ class Paranoiac < ActiveRecord::Base
94
+ acts_as_paranoid :recover_dependent_associations => false
95
+ end
96
+ ```
66
97
 
67
- By default when using timestamp fields to mark deletion, dependent records will be recovered if they were deleted within 5 seconds of the object upon which they depend. This restores the objects to the state before the recursive deletion without restoring other objects that were deleted earlier. This window can be changed with the `dependent_recovery_window` option
98
+ By default, dependent records will be recovered if they were deleted within 2 minutes of the object upon which they depend. This restores the objects to the state before the recursive deletion without restoring other objects that were deleted earlier. The behavior is only available when both parent and dependant are using timestamp fields to mark deletion, which is the default behavior. This window can be changed with the `dependent_recovery_window` option:
68
99
 
69
- class Paranoiac < ActiveRecord::Base
70
- acts_as_paranoid
71
- has_many :paranoids, :dependent => :destroy
72
- end
100
+ ```ruby
101
+ class Paranoiac < ActiveRecord::Base
102
+ acts_as_paranoid
103
+ has_many :paranoids, :dependent => :destroy
104
+ end
73
105
 
74
- class Paranoid < ActiveRecord::Base
75
- belongs_to :paranoic
106
+ class Paranoid < ActiveRecord::Base
107
+ belongs_to :paranoic
76
108
 
77
- # Paranoid objects will be recovered alongside Paranoic objects
78
- # if they were deleted within 1 minute of the Paranoic object
79
- acts_as_paranoid :dependent_recovery_window => 1.minute
80
- end
109
+ # Paranoid objects will be recovered alongside Paranoic objects
110
+ # if they were deleted within 10 minutes of the Paranoic object
111
+ acts_as_paranoid :dependent_recovery_window => 10.minutes
112
+ end
113
+ ```
81
114
 
82
115
  or in the recover statement
83
116
 
84
- Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recovery_window => 30.seconds)
117
+ ```ruby
118
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recovery_window => 30.seconds)
119
+ ```
85
120
 
86
121
  ### Validation
87
122
  ActiveRecord's built-in uniqueness validation does not account for records deleted by ActsAsParanoid. If you want to check for uniqueness among non-deleted records only, use the macro `validates_as_paranoid` in your model. Then, instead of using `validates_uniqueness_of`, use `validates_uniqueness_of_without_deleted`. This will keep deleted records from counting against the uniqueness check.
88
123
 
89
- class Paranoiac < ActiveRecord::Base
90
- acts_as_paranoid
91
- validates_as_paranoid
92
- validates_uniqueness_of_without_deleted :name
93
- end
124
+ ```ruby
125
+ class Paranoiac < ActiveRecord::Base
126
+ acts_as_paranoid
127
+ validates_as_paranoid
128
+ validates_uniqueness_of_without_deleted :name
129
+ end
94
130
 
95
- Paranoiac.create(:name => 'foo').destroy
96
- Paranoiac.new(:name => 'foo').valid? #=> true
131
+ p1 = Paranoiac.create(:name => 'foo')
132
+ p1.destroy
97
133
 
134
+ p2 = Paranoiac.new(:name => 'foo')
135
+ p2.valid? #=> true
136
+ p2.save
137
+
138
+ p1.recover #=> fails validation!
139
+ ```
98
140
 
99
141
  ### Status
100
- Once you retrieve data using `with_deleted` scope you can check deletion status using `deleted?` helper:
142
+ You can check the status of your paranoid objects with the `deleted?` helper
101
143
 
102
- Paranoiac.create(:name => 'foo').destroy
103
- Paranoiac.with_deleted.first.deleted? #=> true
144
+ ```ruby
145
+ Paranoiac.create(:name => 'foo').destroy
146
+ Paranoiac.with_deleted.first.deleted? #=> true
147
+ ```
148
+
149
+ ### Scopes
150
+
151
+ As you've probably guessed, `with_deleted` and `only_deleted` are scopes. You can, however, chain them freely with other scopes you might have. This
152
+
153
+ ```ruby
154
+ Paranoiac.pretty.with_deleted
155
+ ```
156
+
157
+ is exactly the same as
158
+
159
+ ```ruby
160
+ Paranoiac.with_deleted.pretty
161
+ ```
162
+
163
+ You can work freely with scopes and it will just work:
164
+
165
+ ```ruby
166
+ class Paranoiac < ActiveRecord::Base
167
+ acts_as_paranoid
168
+ scope :pretty, where(:pretty => true)
169
+ end
170
+
171
+ Paranoiac.create(:pretty => true)
172
+
173
+ Paranoiac.pretty.count #=> 1
174
+ Paranoiac.only_deleted.count #=> 0
175
+ Paranoiac.pretty.only_deleted.count #=> 0
176
+
177
+ Paranoiac.first.destroy
178
+
179
+ Paranoiac.pretty.count #=> 0
180
+ Paranoiac.only_deleted.count #=> 1
181
+ Paranoiac.pretty.only_deleted.count #=> 1
182
+ ```
183
+
184
+ ### Associations
185
+
186
+ 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
+
188
+ ```ruby
189
+ class ParanoiacParent < ActiveRecord::Base
190
+ has_many :children, :class_name => "ParanoiacChild"
191
+ end
192
+
193
+ class ParanoiacChild < ActiveRecord::Base
194
+ belongs_to :parent, :class_name => "ParanoiacParent"
195
+ belongs_to :parent_with_deleted, :class_name => "ParanoiacParent", :with_deleted => true
196
+ end
197
+
198
+ parent = ParanoiacParent.first
199
+ child = parent.children.create
200
+ parent.destroy
201
+
202
+ child.parent #=> nil
203
+ child.parent_with_deleted #=> ParanoiacParent (it works!)
204
+ ```
104
205
 
105
206
  ## Caveats
106
207
 
107
208
  Watch out for these caveats:
108
209
 
109
- - You cannot use default\_scope in your model. It is possible to work around this caveat, but it's not pretty. Have a look at [this article](http://joshuaclayton.github.com/code/default_scope/activerecord/is_paranoid/multiple-default-scopes.html) if you really need to have your own default scope.
110
- - You cannot use scopes named `with_deleted`, `only_deleted` and `paranoid_deleted_around_time`
210
+ - You cannot use scopes named `with_deleted` and `only_deleted`
211
+ - You cannot use scopes named `deleted_inside_time_window`, `deleted_before_time`, `deleted_after_time` **if** your paranoid column's type is `time`
111
212
  - `unscoped` will return all records, deleted or not
112
213
 
113
- ## Acknowledgements
214
+ # Support
215
+
216
+ This gem supports the most recent versions of Rails and Ruby.
217
+
218
+ ## Rails
219
+
220
+ 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
+ gem "rails3_acts_as_paranoid", "~>0.2.0"
223
+
224
+ 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
+ gem "rails3_acts_as_paranoid", "~>0.1.4"
227
+
228
+ 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
+ gem "rails3_acts_as_paranoid", "~>0.0.9"
231
+
232
+
233
+ ## Ruby
234
+
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
+
237
+ # Acknowledgements
114
238
 
115
239
  * To [cheerfulstoic](https://github.com/cheerfulstoic) for adding recursive recovery
116
240
  * To [Jonathan Vaught](https://github.com/gravelpup) for adding paranoid validations
@@ -118,5 +242,7 @@ Watch out for these caveats:
118
242
  * To [flah00](https://github.com/flah00) for adding support for STI-based associations (with :dependent)
119
243
  * To [vikramdhillon](https://github.com/vikramdhillon) for the idea and
120
244
  initial implementation of support for string column type
245
+ * To [Craig Walker](https://github.com/softcraft-development) for Rails 3.1 support and fixing various pending issues
246
+ * To [Charles G.](https://github.com/chuckg) for Rails 3.2 support and for making a desperately needed global code refactoring
121
247
 
122
248
  Copyright © 2010 Gonçalo Silva, released under the MIT license
@@ -0,0 +1,31 @@
1
+ module ActsAsParanoid
2
+ module Associations
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.class_eval do
6
+ class << self
7
+ alias_method_chain :belongs_to, :deleted
8
+ end
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def belongs_to_with_deleted(target, options = {})
14
+ with_deleted = options.delete(:with_deleted)
15
+ result = belongs_to_without_deleted(target, options)
16
+
17
+ if with_deleted
18
+ class_eval <<-RUBY, __FILE__, __LINE__
19
+ def #{target}_with_unscoped(*args)
20
+ return #{target}_without_unscoped(*args) unless association(:#{target}).klass.paranoid?
21
+ association(:#{target}).klass.with_deleted.scoping { #{target}_without_unscoped(*args) }
22
+ end
23
+ alias_method_chain :#{target}, :unscoped
24
+ RUBY
25
+ end
26
+
27
+ result
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,157 @@
1
+ module ActsAsParanoid
2
+ module Core
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ base.define_callbacks :recover
10
+ end
11
+
12
+ def before_recover(method)
13
+ set_callback :recover, :before, method
14
+ end
15
+
16
+ def after_recover(method)
17
+ set_callback :recover, :after, method
18
+ end
19
+
20
+ def with_deleted
21
+ without_paranoid_default_scope
22
+ end
23
+
24
+ def only_deleted
25
+ without_paranoid_default_scope.where("#{paranoid_column_reference} IS NOT ?", nil)
26
+ end
27
+
28
+ def delete_all!(conditions = nil)
29
+ without_paranoid_default_scope.delete_all!(conditions)
30
+ end
31
+
32
+ def delete_all(conditions = nil)
33
+ update_all ["#{paranoid_configuration[:column]} = ?", delete_now_value], conditions
34
+ end
35
+
36
+ def paranoid_default_scope_sql
37
+ self.scoped.table[paranoid_column].eq(nil).to_sql
38
+ end
39
+
40
+ def paranoid_column
41
+ paranoid_configuration[:column].to_sym
42
+ end
43
+
44
+ def paranoid_column_type
45
+ paranoid_configuration[:column_type].to_sym
46
+ end
47
+
48
+ def dependent_associations
49
+ self.reflect_on_all_associations.select {|a| [:destroy, :delete_all].include?(a.options[:dependent]) }
50
+ end
51
+
52
+ def delete_now_value
53
+ case paranoid_configuration[:column_type]
54
+ when "time" then Time.now
55
+ when "boolean" then true
56
+ when "string" then paranoid_configuration[:deleted_value]
57
+ end
58
+ end
59
+
60
+ protected
61
+
62
+ def without_paranoid_default_scope
63
+ scope = self.scoped.with_default_scope
64
+ scope.where_values.delete(paranoid_default_scope_sql)
65
+
66
+ scope
67
+ end
68
+ end
69
+
70
+ def paranoid_value
71
+ self.send(self.class.paranoid_column)
72
+ end
73
+
74
+ def destroy!
75
+ with_transaction_returning_status do
76
+ run_callbacks :destroy do
77
+ destroy_dependent_associations!
78
+ self.class.delete_all!(self.class.primary_key.to_sym => self.id)
79
+ self.paranoid_value = self.class.delete_now_value
80
+ freeze
81
+ end
82
+ end
83
+ end
84
+
85
+ def destroy
86
+ if paranoid_value.nil?
87
+ with_transaction_returning_status do
88
+ run_callbacks :destroy do
89
+ self.class.delete_all(self.class.primary_key.to_sym => self.id)
90
+ self.paranoid_value = self.class.delete_now_value
91
+ self
92
+ end
93
+ end
94
+ else
95
+ destroy!
96
+ end
97
+ end
98
+
99
+ def recover(options={})
100
+ options = {
101
+ :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
102
+ :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window]
103
+ }.merge(options)
104
+
105
+ self.class.transaction do
106
+ run_callbacks :recover do
107
+ recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
108
+
109
+ self.paranoid_value = nil
110
+ self.save
111
+ end
112
+ end
113
+ end
114
+
115
+ def recover_dependent_associations(window, options)
116
+ self.class.dependent_associations.each do |reflection|
117
+ next unless reflection.klass.paranoid?
118
+
119
+ scope = reflection.klass.only_deleted
120
+
121
+ # Merge in the association's scope
122
+ scope = scope.merge(association(reflection.name).association_scope)
123
+
124
+ # We can only recover by window if both parent and dependant have a
125
+ # paranoid column type of :time.
126
+ if self.class.paranoid_column_type == :time && reflection.klass.paranoid_column_type == :time
127
+ scope = scope.merge(reflection.klass.deleted_inside_time_window(paranoid_value, window))
128
+ end
129
+
130
+ scope.each do |object|
131
+ object.recover(options)
132
+ end
133
+ end
134
+ end
135
+
136
+ def destroy_dependent_associations!
137
+ self.class.dependent_associations.each do |association|
138
+ if association.collection? && self.send(association.name).paranoid?
139
+ association.klass.with_deleted.instance_eval("find_all_by_#{association.foreign_key}(#{self.id.to_json})").each do |object|
140
+ object.destroy!
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def deleted?
147
+ !paranoid_value.nil?
148
+ end
149
+ alias_method :destroyed?, :deleted?
150
+
151
+ private
152
+
153
+ def paranoid_value=(value)
154
+ self.send("#{self.class.paranoid_column}=", value)
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,37 @@
1
+ module ActsAsParanoid
2
+ module Relation
3
+ def self.included(base)
4
+ base.class_eval do
5
+
6
+ def paranoid?
7
+ klass.try(:paranoid?) ? true : false
8
+ end
9
+
10
+ def paranoid_deletion_attributes
11
+ { klass.paranoid_column => klass.delete_now_value }
12
+ end
13
+
14
+ alias_method :orig_delete_all, :delete_all
15
+ def delete_all!(conditions = nil)
16
+ if conditions
17
+ where(conditions).delete_all!
18
+ else
19
+ orig_delete_all
20
+ end
21
+ end
22
+
23
+ def delete_all(conditions = nil)
24
+ if paranoid?
25
+ update_all(paranoid_deletion_attributes, conditions)
26
+ else
27
+ delete_all!(conditions)
28
+ end
29
+ end
30
+
31
+ def destroy!(id_or_array)
32
+ where(primary_key => id_or_array).orig_delete_all
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActsAsParanoid
4
+ module Validations
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ class UniquenessWithoutDeletedValidator < ActiveRecord::Validations::UniquenessValidator
10
+ def validate_each(record, attribute, value)
11
+ finder_class = find_finder_class_for(record)
12
+ table = finder_class.arel_table
13
+
14
+ coder = record.class.serialized_attributes[attribute.to_s]
15
+
16
+ if value && coder
17
+ value = coder.dump value
18
+ end
19
+
20
+ relation = build_relation(finder_class, table, attribute, value)
21
+ relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
22
+
23
+ Array.wrap(options[:scope]).each do |scope_item|
24
+ scope_value = record.send(scope_item)
25
+ relation = relation.and(table[scope_item].eq(scope_value))
26
+ end
27
+
28
+ # Re-add ActsAsParanoid default scope conditions manually.
29
+ if finder_class.unscoped.where(finder_class.paranoid_default_scope_sql).where(relation).exists?
30
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
31
+ end
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ def validates_uniqueness_of_without_deleted(*attr_names)
37
+ validates_with UniquenessWithoutDeletedValidator, _merge_attributes(attr_names)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,66 +1,20 @@
1
- require 'active_record'
2
- require 'validations/uniqueness_without_deleted'
1
+ require 'active_record/base'
2
+ require 'active_record/relation'
3
+ require 'active_record/callbacks'
4
+ require 'acts_as_paranoid/core'
5
+ require 'acts_as_paranoid/associations'
6
+ require 'acts_as_paranoid/validations'
7
+ require 'acts_as_paranoid/relation'
3
8
 
4
9
 
5
- module ActiveRecord
6
- class Relation
7
- def paranoid?
8
- klass.try(:paranoid?) ? true : false
9
- end
10
-
11
- def paranoid_deletion_attributes
12
- { klass.paranoid_column => klass.delete_now_value }
13
- end
14
-
15
- alias_method :destroy!, :destroy
16
- def destroy(id)
17
- if paranoid?
18
- update_all(paranoid_deletion_attributes, {:id => id})
19
- else
20
- destroy!(id)
21
- end
22
- end
23
-
24
- alias_method :really_delete_all!, :delete_all
25
-
26
- def delete_all!(conditions = nil)
27
- if conditions
28
- # This idea comes out of Rails 3.1 ActiveRecord::Record.delete_all
29
- where(conditions).delete_all!
30
- else
31
- really_delete_all!
32
- end
33
- end
34
-
35
- def delete_all(conditions = nil)
36
- if paranoid?
37
- update_all(paranoid_deletion_attributes, conditions)
38
- else
39
- delete_all!(conditions)
40
- end
41
- end
42
-
43
- def arel=(a)
44
- @arel = a
45
- end
46
-
47
- def with_deleted
48
- wd = self.clone
49
- wd.default_scoped = false
50
- wd.arel = self.build_arel
51
- wd
52
- end
53
- end
54
- end
55
-
56
10
  module ActsAsParanoid
57
11
 
58
12
  def paranoid?
59
- self.included_modules.include?(InstanceMethods)
13
+ self.included_modules.include?(ActsAsParanoid::Core)
60
14
  end
61
15
 
62
16
  def validates_as_paranoid
63
- extend ParanoidValidations::ClassMethods
17
+ include ActsAsParanoid::Validations
64
18
  end
65
19
 
66
20
  def acts_as_paranoid(options = {})
@@ -78,203 +32,28 @@ module ActsAsParanoid
78
32
 
79
33
  return if paranoid?
80
34
 
81
- # Magic!
82
- default_scope where("#{paranoid_column_reference} IS ?", nil)
83
-
84
- scope :paranoid_deleted_around_time, lambda {|value, window|
85
- if self.class.respond_to?(:paranoid?) && self.class.paranoid?
86
- if self.class.paranoid_column_type == 'time' && ![true, false].include?(value)
87
- self.where("#{self.class.paranoid_column} > ? AND #{self.class.paranoid_column} < ?", (value - window), (value + window))
88
- else
89
- self.only_deleted
90
- end
91
- end if paranoid_configuration[:column_type] == 'time'
92
- }
93
-
94
- include InstanceMethods
95
- extend ClassMethods
96
- end
97
-
98
- module ClassMethods
99
- def self.extended(base)
100
- base.define_callbacks :recover
101
- end
102
-
103
- def before_recover(method)
104
- set_callback :recover, :before, method
105
- end
106
-
107
- def after_recover(method)
108
- set_callback :recover, :after, method
109
- end
110
-
111
- def with_deleted
112
- self.unscoped
113
- end
114
-
115
- def only_deleted
116
- self.unscoped.where("#{paranoid_column_reference} IS NOT ?", nil)
117
- end
118
-
119
- def deletion_conditions(id_or_array)
120
- ["id in (?)", [id_or_array].flatten]
121
- end
122
-
123
- def delete!(id_or_array)
124
- delete_all!(deletion_conditions(id_or_array))
125
- end
126
-
127
- def delete(id_or_array)
128
- delete_all(deletion_conditions(id_or_array))
129
- end
130
-
131
- def delete_all!(conditions = nil)
132
- self.unscoped.delete_all!(conditions)
133
- end
134
-
135
- def delete_all(conditions = nil)
136
- update_all ["#{paranoid_configuration[:column]} = ?", delete_now_value], conditions
137
- end
138
-
139
- def paranoid_column
140
- paranoid_configuration[:column].to_sym
141
- end
142
-
143
- def paranoid_column_type
144
- paranoid_configuration[:column_type].to_sym
145
- end
146
-
147
- def dependent_associations
148
- self.reflect_on_all_associations.select {|a| [:destroy, :delete_all].include?(a.options[:dependent]) }
149
- end
150
-
151
- def delete_now_value
152
- case paranoid_configuration[:column_type]
153
- when "time" then Time.now
154
- when "boolean" then true
155
- when "string" then paranoid_configuration[:deleted_value]
156
- end
157
- end
158
- end
159
-
160
- module InstanceMethods
161
-
162
- def paranoid_value
163
- self.send(self.class.paranoid_column)
164
- end
35
+ include ActsAsParanoid::Core
36
+ include ActsAsParanoid::Associations
165
37
 
166
- def destroy!
167
- with_transaction_returning_status do
168
- run_callbacks :destroy do
169
- act_on_dependent_destroy_associations
170
- self.class.delete_all!(self.class.primary_key.to_sym => self.id)
171
- self.paranoid_value = self.class.delete_now_value
172
- freeze
173
- end
174
- end
175
- end
176
-
177
- def destroy
178
- if paranoid_value.nil?
179
- with_transaction_returning_status do
180
- run_callbacks :destroy do
181
- self.class.delete_all(self.class.primary_key.to_sym => self.id)
182
- self.paranoid_value = self.class.delete_now_value
183
- self
184
- end
185
- end
186
- else
187
- destroy!
188
- end
189
- end
190
-
191
- def delete!
192
- with_transaction_returning_status do
193
- act_on_dependent_destroy_associations
194
- self.class.delete_all!(self.class.primary_key.to_sym => self.id)
195
- self.paranoid_value = self.class.delete_now_value
196
- freeze
197
- end
198
- end
199
-
200
- def delete
201
- if paranoid_value.nil?
202
- with_transaction_returning_status do
203
- self.class.delete_all(self.class.primary_key.to_sym => self.id)
204
- self.paranoid_value = self.class.delete_now_value
205
- self
206
- end
207
- else
208
- delete!
209
- end
210
- end
211
-
212
- def recover(options={})
213
- options = {
214
- :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
215
- :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window]
216
- }.merge(options)
217
-
218
- self.class.transaction do
219
- run_callbacks :recover do
220
- recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
221
-
222
- self.paranoid_value = nil
223
- self.save
224
- end
225
- end
226
- end
38
+ # Magic!
39
+ default_scope { where(paranoid_default_scope_sql) }
227
40
 
228
- def recover_dependent_associations(window, options)
229
- self.class.dependent_associations.each do |association|
230
- if association.collection? && self.send(association.name).paranoid?
231
- self.send(association.name).unscoped do
232
- self.send(association.name).paranoid_deleted_around_time(paranoid_value, window).each do |object|
233
- object.recover(options) if object.respond_to?(:recover)
234
- end
235
- end
236
- elsif association.macro == :has_one && association.klass.paranoid?
237
- association.klass.unscoped do
238
- object = association.klass.paranoid_deleted_around_time(paranoid_value, window).send('find_by_'+association.foreign_key, self.id)
239
- object.recover(options) if object && object.respond_to?(:recover)
240
- end
241
- elsif association.klass.paranoid?
242
- association.klass.unscoped do
243
- id = self.send(association.foreign_key)
244
- object = association.klass.paranoid_deleted_around_time(paranoid_value, window).find_by_id(id)
245
- object.recover(options) if object && object.respond_to?(:recover)
246
- end
247
- end
248
- end
249
- end
250
-
251
- def act_on_dependent_destroy_associations
252
- self.class.dependent_associations.each do |association|
253
- if association.collection? && self.send(association.name).paranoid?
254
- association.klass.with_deleted.instance_eval("find_all_by_#{association.foreign_key}(#{self.id.to_json})").each do |object|
255
- object.destroy!
256
- end
257
- end
258
- end
259
- end
41
+ if paranoid_configuration[:column_type] == 'time'
42
+ scope :deleted_inside_time_window, lambda {|time, window|
43
+ deleted_after_time((time - window)).deleted_before_time((time + window))
44
+ }
260
45
 
261
- def deleted?
262
- !paranoid_value.nil?
263
- end
264
- alias_method :destroyed?, :deleted?
265
-
266
- private
267
- def paranoid_value=(value)
268
- self.send("#{self.class.paranoid_column}=", value)
46
+ scope :deleted_after_time, lambda { |time| where("#{paranoid_column} > ?", time) }
47
+ scope :deleted_before_time, lambda { |time| where("#{paranoid_column} < ?", time) }
269
48
  end
270
-
271
49
  end
272
-
273
50
  end
274
51
 
275
-
276
52
  # Extend ActiveRecord's functionality
277
53
  ActiveRecord::Base.send :extend, ActsAsParanoid
278
54
 
55
+ # Override ActiveRecord::Relation's behavior
56
+ ActiveRecord::Relation.send :include, ActsAsParanoid::Relation
57
+
279
58
  # Push the recover callback onto the activerecord callback list
280
59
  ActiveRecord::Callbacks::CALLBACKS.push(:before_recover, :after_recover)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails3_acts_as_paranoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-06 00:00:00.000000000Z
12
+ date: 2012-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &70311543517160 !ruby/object:Gem::Requirement
16
+ requirement: &70201325143880 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '3.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70311543517160
24
+ version_requirements: *70201325143880
25
25
  description: Active Record (~>3.2) plugin which allows you to hide and restore records
26
26
  without actually deleting them. Check its GitHub page for more in-depth information.
27
27
  email:
@@ -30,11 +30,14 @@ executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - lib/acts_as_paranoid/associations.rb
34
+ - lib/acts_as_paranoid/core.rb
35
+ - lib/acts_as_paranoid/relation.rb
36
+ - lib/acts_as_paranoid/validations.rb
33
37
  - lib/rails3_acts_as_paranoid.rb
34
- - lib/validations/uniqueness_without_deleted.rb
35
38
  - LICENSE
36
39
  - README.markdown
37
- homepage: https://github.com/softcraft-development/rails3_acts_as_paranoid
40
+ homepage: https://github.com/goncalossilva/rails3_acts_as_paranoid
38
41
  licenses: []
39
42
  post_install_message:
40
43
  rdoc_options: []
@@ -48,7 +51,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
48
51
  version: '0'
49
52
  segments:
50
53
  - 0
51
- hash: -1615322302278310940
54
+ hash: -2548769094078379763
52
55
  required_rubygems_version: !ruby/object:Gem::Requirement
53
56
  none: false
54
57
  requirements:
@@ -57,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
57
60
  version: 1.3.6
58
61
  requirements: []
59
62
  rubyforge_project: rails3_acts_as_paranoid
60
- rubygems_version: 1.8.10
63
+ rubygems_version: 1.8.17
61
64
  signing_key:
62
65
  specification_version: 3
63
66
  summary: Active Record (~>3.2) plugin which allows you to hide and restore records
@@ -1,39 +0,0 @@
1
- require 'active_support/core_ext/array/wrap'
2
-
3
- module ParanoidValidations
4
- class UniquenessWithoutDeletedValidator < ActiveRecord::Validations::UniquenessValidator
5
- def validate_each(record, attribute, value)
6
- finder_class = find_finder_class_for(record)
7
-
8
- if value && record.class.serialized_attributes.key?(attribute.to_s)
9
- value = YAML.dump value
10
- end
11
-
12
- table = Arel::Table.new(record.class.table_name)
13
- sql, params = build_relation(finder_class, table, attribute, value)
14
-
15
- # This is the only changed line from the base class version - it does finder_class.unscoped
16
- relation = finder_class.where(sql, *params)
17
-
18
- Array.wrap(options[:scope]).each do |scope_item|
19
- scope_value = record.send(scope_item)
20
- relation = relation.where(scope_item => scope_value)
21
- end
22
-
23
- if record.persisted?
24
- # TODO : This should be in Arel
25
- relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
26
- end
27
-
28
- if relation.exists?
29
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
30
- end
31
- end
32
- end
33
-
34
- module ClassMethods
35
- def validates_uniqueness_of_without_deleted(*attr_names)
36
- validates_with UniquenessWithoutDeletedValidator, _merge_attributes(attr_names)
37
- end
38
- end
39
- end