rails3_acts_as_paranoid_create 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ pkg
2
+ .bundle
3
+ .rvmrc
4
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rails3_acts_as_paranoid.gemspec
4
+ gemspec
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rails3_acts_as_paranoid_create (0.2.0)
5
+ activerecord (~> 3.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ activemodel (3.1.0)
11
+ activesupport (= 3.1.0)
12
+ bcrypt-ruby (~> 3.0.0)
13
+ builder (~> 3.0.0)
14
+ i18n (~> 0.6)
15
+ activerecord (3.1.0)
16
+ activemodel (= 3.1.0)
17
+ activesupport (= 3.1.0)
18
+ arel (~> 2.2.1)
19
+ tzinfo (~> 0.3.29)
20
+ activesupport (3.1.0)
21
+ multi_json (~> 1.0)
22
+ arel (2.2.1)
23
+ bcrypt-ruby (3.0.0)
24
+ builder (3.0.0)
25
+ i18n (0.6.0)
26
+ multi_json (1.0.3)
27
+ rake (0.9.2)
28
+ rdoc (3.9.4)
29
+ sqlite3 (1.3.4)
30
+ sqlite3-ruby (1.3.3)
31
+ sqlite3 (>= 1.3.3)
32
+ tzinfo (0.3.29)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ activesupport (>= 3.1.0)
39
+ rails3_acts_as_paranoid_create!
40
+ rake (>= 0.9.0)
41
+ rdoc (>= 3.9.0)
42
+ sqlite3-ruby (>= 1.3.0)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Gonçalo Silva
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ # ActsAsParanoid
2
+
3
+ A simple plugin which hides records instead of deleting them, being able to recover them.
4
+
5
+ ## Credits
6
+
7
+ 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).
8
+
9
+ 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.
10
+
11
+ ## Usage
12
+
13
+ You can enable ActsAsParanoid like this:
14
+
15
+ class Paranoiac < ActiveRecord::Base
16
+ acts_as_paranoid
17
+ end
18
+
19
+ ### Options
20
+
21
+ You can also specify the name of the column to store it's *deletion* and the type of data it holds:
22
+
23
+ - :column => 'deleted_at'
24
+ - :type => 'time'
25
+
26
+ 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".
27
+
28
+ 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").
29
+
30
+ ### Filtering
31
+
32
+ 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:
33
+
34
+ Paranoiac.only_deleted # retrieves the deleted records
35
+ Paranoiac.with_deleted # retrieves all records, deleted or not
36
+
37
+ ### Real deletion
38
+
39
+ In order to really delete a record, just use:
40
+
41
+ paranoiac.destroy!
42
+ Paranoiac.delete_all!(conditions)
43
+
44
+ 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:
45
+
46
+ Paranoiac.first.destroy # does NOT delete the first record, just hides it
47
+ Paranoiac.only_deleted.destroy # deletes the first record from the database
48
+
49
+ ### Recovery
50
+
51
+ Recovery is easy. Just invoke `recover` on it, like this:
52
+
53
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover
54
+
55
+ 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:
56
+
57
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recursive => false)
58
+
59
+ If you would like to change the default behavior for a model, you can use the `recover_dependent_associations` option
60
+
61
+ class Paranoiac < ActiveRecord::Base
62
+ acts_as_paranoid :recover_dependent_associations => false
63
+ end
64
+
65
+ 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
66
+
67
+ class Paranoiac < ActiveRecord::Base
68
+ acts_as_paranoid
69
+ has_many :paranoids, :dependent => :destroy
70
+ end
71
+
72
+ class Paranoid < ActiveRecord::Base
73
+ belongs_to :paranoic
74
+
75
+ # Paranoid objects will be recovered alongside Paranoic objects
76
+ # if they were deleted within 1 minute of the Paranoic object
77
+ acts_as_paranoid :dependent_recovery_window => 1.minute
78
+ end
79
+
80
+ or in the recover statement
81
+
82
+ Paranoiac.only_deleted.where("name = ?", "not dead yet").first.recover(:recovery_window => 30.seconds)
83
+
84
+ ### Validation
85
+ 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.
86
+
87
+ class Paranoiac < ActiveRecord::Base
88
+ acts_as_paranoid
89
+ validates_as_paranoid
90
+ validates_uniqueness_of_without_deleted :name
91
+ end
92
+
93
+ Paranoiac.create(:name => 'foo').destroy
94
+ Paranoiac.new(:name => 'foo').valid? #=> true
95
+
96
+
97
+ ### Status
98
+ Once you retrieve data using `with_deleted` scope you can check deletion status using `deleted?` helper:
99
+
100
+ Paranoiac.create(:name => 'foo').destroy
101
+ Paranoiac.with_deleted.first.deleted? #=> true
102
+
103
+ ## Caveats
104
+
105
+ Watch out for these caveats:
106
+
107
+ - 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.
108
+ - You cannot use scopes named `with_deleted`, `only_deleted` and `paranoid_deleted_around_time`
109
+ - `unscoped` will return all records, deleted or not
110
+
111
+ ## Acknowledgements
112
+
113
+ * To [cheerfulstoic](https://github.com/cheerfulstoic) for adding recursive recovery
114
+ * To [Jonathan Vaught](https://github.com/gravelpup) for adding paranoid validations
115
+ * To [Geoffrey Hichborn](https://github.com/phene) for improving the overral code quality and adding support for after_commit
116
+ * To [flah00](https://github.com/flah00) for adding support for STI-based associations (with :dependent)
117
+ * To [vikramdhillon](https://github.com/vikramdhillon) for the idea and
118
+ initial implementation of support for string column type
119
+
120
+ Copyright © 2010 Gonçalo Silva, released under the MIT license
@@ -0,0 +1,48 @@
1
+ require('bundler/gem_tasks')
2
+ require "rake/testtask"
3
+ require "rdoc/task"
4
+
5
+ gemspec = eval(File.read(Dir["*.gemspec"].first))
6
+
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the rails3_acts_as_paranoid plugin.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = true
17
+ end
18
+
19
+ desc 'Generate documentation for the rails3_acts_as_paranoid plugin.'
20
+ Rake::RDocTask.new(:rdoc) do |rdoc|
21
+ rdoc.rdoc_dir = 'rdoc'
22
+ rdoc.title = 'ActsAsParanoid'
23
+ rdoc.options << '--line-numbers' << '--inline-source'
24
+ rdoc.rdoc_files.include('README')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
27
+
28
+ desc "Validate the gemspec"
29
+ task :gemspec do
30
+ gemspec.validate
31
+ end
32
+
33
+ desc "Build gem locally"
34
+ task :build => :gemspec do
35
+ system "gem build #{gemspec.name}.gemspec"
36
+ FileUtils.mkdir_p "pkg"
37
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", "pkg"
38
+ end
39
+
40
+ desc "Install gem locally"
41
+ task :install => :build do
42
+ system "gem install pkg/#{gemspec.name}-#{gemspec.version}"
43
+ end
44
+
45
+ desc "Clean automatically generated files"
46
+ task :clean do
47
+ FileUtils.rm_rf "pkg"
48
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'rails3_acts_as_paranoid'
@@ -0,0 +1,197 @@
1
+ require 'active_record'
2
+ require 'validations/uniqueness_without_deleted'
3
+
4
+ module ActsAsParanoid
5
+
6
+ def paranoid?
7
+ self.included_modules.include?(InstanceMethods)
8
+ end
9
+
10
+ def validates_as_paranoid
11
+ extend ParanoidValidations::ClassMethods
12
+ end
13
+
14
+ def acts_as_paranoid(options = {})
15
+ raise ArgumentError, "Hash expected, got #{options.class.name}" if not options.is_a?(Hash) and not options.empty?
16
+
17
+ class_attribute :paranoid_configuration, :paranoid_column_reference
18
+
19
+ self.paranoid_configuration = { :column => "deleted_at", :column_type => "time", :recover_dependent_associations => true, :dependent_recovery_window => 2.minutes }
20
+ self.paranoid_configuration.merge!({ :deleted_value => "deleted" }) if options[:column_type] == "string"
21
+ self.paranoid_configuration.merge!(options) # user options
22
+
23
+ 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]
24
+
25
+ self.paranoid_column_reference = "#{self.table_name}.#{paranoid_configuration[:column]}"
26
+
27
+ return if paranoid?
28
+
29
+ ActiveRecord::Relation.class_eval do
30
+ alias_method :delete_all!, :delete_all
31
+ alias_method :destroy!, :destroy
32
+ end
33
+
34
+ scope :not_deleted, where("#{paranoid_column_reference} IS ?", nil)
35
+
36
+ scope :paranoid_deleted_around_time, lambda {|value, window|
37
+ if self.class.respond_to?(:paranoid?) && self.class.paranoid?
38
+ if self.class.paranoid_column_type == 'time' && ![true, false].include?(value)
39
+ self.where("#{self.class.paranoid_column} > ? AND #{self.class.paranoid_column} < ?", (value - window), (value + window))
40
+ else
41
+ self.only_deleted
42
+ end
43
+ end if paranoid_configuration[:column_type] == 'time'
44
+ }
45
+
46
+ include InstanceMethods
47
+ extend ClassMethods
48
+ end
49
+
50
+ module ClassMethods
51
+ def self.extended(base)
52
+ base.define_callbacks :recover
53
+ end
54
+
55
+ def before_recover(method)
56
+ set_callback :recover, :before, method
57
+ end
58
+
59
+ def after_recover(method)
60
+ set_callback :recover, :after, method
61
+ end
62
+
63
+ def with_deleted
64
+ self.unscoped
65
+ end
66
+
67
+ def only_deleted
68
+ self.unscoped.where("#{paranoid_column_reference} IS NOT ?", nil)
69
+ end
70
+
71
+ def delete_all!(conditions = nil)
72
+ self.unscoped.delete_all!(conditions)
73
+ end
74
+
75
+ def delete_all(conditions = nil)
76
+ update_all ["#{paranoid_configuration[:column]} = ?", delete_now_value], conditions
77
+ end
78
+
79
+ def paranoid_column
80
+ paranoid_configuration[:column].to_sym
81
+ end
82
+
83
+ def paranoid_column_type
84
+ paranoid_configuration[:column_type].to_sym
85
+ end
86
+
87
+ def dependent_associations
88
+ self.reflect_on_all_associations.select {|a| [:destroy, :delete_all].include?(a.options[:dependent]) }
89
+ end
90
+
91
+ def delete_now_value
92
+ case paranoid_configuration[:column_type]
93
+ when "time" then Time.now
94
+ when "boolean" then true
95
+ when "string" then paranoid_configuration[:deleted_value]
96
+ end
97
+ end
98
+ end
99
+
100
+ module InstanceMethods
101
+
102
+ def paranoid_value
103
+ self.send(self.class.paranoid_column)
104
+ end
105
+
106
+ def destroy!
107
+ with_transaction_returning_status do
108
+ run_callbacks :destroy do
109
+ act_on_dependent_destroy_associations
110
+ self.class.delete_all!(:id => self.id)
111
+ self.paranoid_value = self.class.delete_now_value
112
+ freeze
113
+ end
114
+ end
115
+ end
116
+
117
+ def destroy
118
+ if paranoid_value.nil?
119
+ with_transaction_returning_status do
120
+ run_callbacks :destroy do
121
+ self.class.delete_all(:id => self.id)
122
+ self.paranoid_value = self.class.delete_now_value
123
+ self
124
+ end
125
+ end
126
+ else
127
+ destroy!
128
+ end
129
+ end
130
+
131
+ def recover(options={})
132
+ options = {
133
+ :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
134
+ :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window]
135
+ }.merge(options)
136
+
137
+ self.class.transaction do
138
+ run_callbacks :recover do
139
+ recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
140
+
141
+ self.update_attributes(self.class.paranoid_column.to_sym => nil)
142
+ end
143
+ end
144
+ end
145
+
146
+ def recover_dependent_associations(window, options)
147
+ self.class.dependent_associations.each do |association|
148
+ if association.collection? && self.send(association.name).paranoid?
149
+ self.send(association.name).unscoped do
150
+ self.send(association.name).paranoid_deleted_around_time(paranoid_value, window).each do |object|
151
+ object.recover(options) if object.respond_to?(:recover)
152
+ end
153
+ end
154
+ elsif association.macro == :has_one && association.klass.paranoid?
155
+ association.klass.unscoped do
156
+ object = association.klass.paranoid_deleted_around_time(paranoid_value, window).send('find_by_'+association.foreign_key, self.id)
157
+ object.recover(options) if object && object.respond_to?(:recover)
158
+ end
159
+ elsif association.klass.paranoid?
160
+ association.klass.unscoped do
161
+ id = self.send(association.foreign_key)
162
+ object = association.klass.paranoid_deleted_around_time(paranoid_value, window).find_by_id(id)
163
+ object.recover(options) if object && object.respond_to?(:recover)
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def act_on_dependent_destroy_associations
170
+ self.class.dependent_associations.each do |association|
171
+ if association.collection? && self.send(association.name).paranoid?
172
+ association.klass.with_deleted.instance_eval("find_all_by_#{association.foreign_key}(#{self.id})").each do |object|
173
+ object.destroy!
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ def deleted?
180
+ !paranoid_value.nil?
181
+ end
182
+ alias_method :destroyed?, :deleted?
183
+
184
+ private
185
+ def paranoid_value=(value)
186
+ self.send("#{self.class.paranoid_column}=", value)
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+
193
+ # Extend ActiveRecord's functionality
194
+ ActiveRecord::Base.send :extend, ActsAsParanoid
195
+
196
+ # Push the recover callback onto the activerecord callback list
197
+ ActiveRecord::Callbacks::CALLBACKS.push(:before_recover, :after_recover)
@@ -0,0 +1,3 @@
1
+ module Rails3ActsAsParanoid
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,38 @@
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
+ sql, params = mount_sql_and_params(finder_class, record.class.quoted_table_name, attribute, value)
13
+
14
+ # This is the only changed line from the base class version - it does finder_class.unscoped
15
+ relation = finder_class.where(sql, *params)
16
+
17
+ Array.wrap(options[:scope]).each do |scope_item|
18
+ scope_value = record.send(scope_item)
19
+ relation = relation.where(scope_item => scope_value)
20
+ end
21
+
22
+ if record.persisted?
23
+ # TODO : This should be in Arel
24
+ relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
25
+ end
26
+
27
+ if relation.exists?
28
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
29
+ end
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ def validates_uniqueness_of_without_deleted(*attr_names)
35
+ validates_with UniquenessWithoutDeletedValidator, _merge_attributes(attr_names)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push(File.expand_path('../lib', __FILE__))
3
+ require('rails3_acts_as_paranoid/version')
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rails3_acts_as_paranoid_create"
7
+ s.version = Rails3ActsAsParanoid::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Goncalo Silva", "Philipp Ullmann"]
10
+ s.email = ["goncalossilva@gmail.com", "philipp.ullmann@create.at"]
11
+ s.homepage = "http://github.com/goncalossilva/rails3_acts_as_paranoid"
12
+ s.summary = "Active Record (>=3.0) plugin which allows you to hide and restore records without actually deleting them."
13
+ s.description = "Active Record (>=3.0) plugin which allows you to hide and restore records without actually deleting them. Check its GitHub page for more in-depth information."
14
+ s.rubyforge_project = s.name
15
+
16
+ s.required_rubygems_version = ">= 1.3.6"
17
+
18
+ s.add_dependency "activerecord", "~> 3.0"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ['lib']
24
+
25
+ s.add_development_dependency('rake', ['>= 0.9.0'])
26
+ s.add_development_dependency('sqlite3-ruby', ['>= 1.3.0'])
27
+ s.add_development_dependency('activesupport', ['>= 3.1.0'])
28
+ s.add_development_dependency('rdoc', ['>= 3.9.0'])
29
+ end
@@ -0,0 +1,295 @@
1
+ require 'test_helper'
2
+
3
+ class ParanoidBaseTest < ActiveSupport::TestCase
4
+ def assert_empty(collection)
5
+ assert(collection.respond_to?(:empty?) && collection.empty?)
6
+ end
7
+
8
+ def setup
9
+ setup_db
10
+
11
+ ["paranoid", "really paranoid", "extremely paranoid"].each do |name|
12
+ ParanoidTime.create! :name => name
13
+ ParanoidBoolean.create! :name => name
14
+ end
15
+
16
+ ParanoidString.create! :name => "strings can be paranoid"
17
+ NotParanoid.create! :name => "no paranoid goals"
18
+ ParanoidWithCallback.create! :name => "paranoid with callbacks"
19
+
20
+ ParanoidObserver.instance.reset
21
+ end
22
+
23
+ def teardown
24
+ teardown_db
25
+ end
26
+ end
27
+
28
+ class ParanoidTest < ParanoidBaseTest
29
+ def test_fake_removal
30
+ assert_equal 3, ParanoidTime.not_deleted.count
31
+ assert_equal 3, ParanoidBoolean.not_deleted.count
32
+ assert_equal 1, ParanoidString.not_deleted.count
33
+
34
+ ParanoidTime.not_deleted.first.destroy
35
+ ParanoidBoolean.delete_all("name = 'paranoid' OR name = 'really paranoid'")
36
+ ParanoidString.not_deleted.first.destroy
37
+ assert_equal 2, ParanoidTime.not_deleted.count
38
+ assert_equal 1, ParanoidBoolean.not_deleted.count
39
+ assert_equal 0, ParanoidString.not_deleted.count
40
+ assert_equal 1, ParanoidTime.only_deleted.count
41
+ assert_equal 2, ParanoidBoolean.only_deleted.count
42
+ assert_equal 1, ParanoidString.only_deleted.count
43
+ assert_equal 3, ParanoidTime.with_deleted.count
44
+ assert_equal 3, ParanoidBoolean.with_deleted.count
45
+ assert_equal 1, ParanoidString.with_deleted.count
46
+ end
47
+
48
+ def test_real_removal
49
+ ParanoidTime.not_deleted.first.destroy!
50
+ ParanoidBoolean.delete_all!("name = 'extremely paranoid' OR name = 'really paranoid'")
51
+ ParanoidString.not_deleted.first.destroy!
52
+ assert_equal 2, ParanoidTime.not_deleted.count
53
+ assert_equal 1, ParanoidBoolean.not_deleted.count
54
+ assert_equal 0, ParanoidString.not_deleted.count
55
+ assert_equal 2, ParanoidTime.with_deleted.count
56
+ assert_equal 1, ParanoidBoolean.with_deleted.count
57
+ assert_equal 0, ParanoidString.with_deleted.count
58
+ assert_equal 0, ParanoidTime.only_deleted.count
59
+ assert_equal 0, ParanoidBoolean.only_deleted.count
60
+ assert_equal 0, ParanoidString.only_deleted.count
61
+
62
+ ParanoidTime.not_deleted.first.destroy
63
+ ParanoidTime.only_deleted.first.destroy
64
+ assert_equal 0, ParanoidTime.only_deleted.count
65
+
66
+ ParanoidTime.delete_all!
67
+ assert_empty ParanoidTime.not_deleted.all
68
+ assert_empty ParanoidTime.with_deleted.all
69
+ end
70
+
71
+ def test_paranoid_scope
72
+ assert_raise(NoMethodError) { NotParanoid.delete_all! }
73
+ assert_raise(NoMethodError) { NotParanoid.first.destroy! }
74
+ assert_raise(NoMethodError) { NotParanoid.with_deleted }
75
+ assert_raise(NoMethodError) { NotParanoid.only_deleted }
76
+ end
77
+
78
+ def test_recovery
79
+ assert_equal 3, ParanoidBoolean.not_deleted.count
80
+ ParanoidBoolean.not_deleted.first.destroy
81
+ assert_equal 2, ParanoidBoolean.not_deleted.count
82
+ ParanoidBoolean.only_deleted.first.recover
83
+ assert_equal 3, ParanoidBoolean.not_deleted.count
84
+
85
+ assert_equal 1, ParanoidString.not_deleted.count
86
+ ParanoidString.not_deleted.first.destroy
87
+ assert_equal 0, ParanoidString.not_deleted.count
88
+ ParanoidString.with_deleted.first.recover
89
+ assert_equal 1, ParanoidString.not_deleted.count
90
+ end
91
+
92
+ def setup_recursive_recovery_tests
93
+ @paranoid_time_object = ParanoidTime.not_deleted.first
94
+
95
+ @paranoid_boolean_count = ParanoidBoolean.not_deleted.count
96
+
97
+ assert_equal 0, ParanoidHasManyDependant.not_deleted.count
98
+ assert_equal 0, ParanoidBelongsDependant.not_deleted.count
99
+
100
+ (1..3).each do |i|
101
+ has_many_object = @paranoid_time_object.paranoid_has_many_dependants.create(:name => "has_many_#{i}")
102
+ has_many_object.create_paranoid_belongs_dependant(:name => "belongs_to_#{i}")
103
+ has_many_object.save
104
+
105
+ paranoid_boolean = @paranoid_time_object.paranoid_booleans.create(:name => "boolean_#{i}")
106
+ paranoid_boolean.create_paranoid_has_one_dependant(:name => "has_one_#{i}")
107
+ paranoid_boolean.save
108
+
109
+ @paranoid_time_object.not_paranoids.create(:name => "not_paranoid_a#{i}")
110
+
111
+ end
112
+
113
+ @paranoid_time_object.create_not_paranoid(:name => "not_paranoid_belongs_to")
114
+
115
+ @paranoid_time_object.create_has_one_not_paranoid(:name => "has_one_not_paranoid")
116
+
117
+ assert_equal 3, ParanoidTime.not_deleted.count
118
+ assert_equal 3, ParanoidHasManyDependant.not_deleted.count
119
+ assert_equal 3, ParanoidBelongsDependant.not_deleted.count
120
+ assert_equal 3, ParanoidHasOneDependant.not_deleted.count
121
+ assert_equal 5, NotParanoid.count
122
+ assert_equal 1, HasOneNotParanoid.count
123
+ assert_equal @paranoid_boolean_count + 3, ParanoidBoolean.not_deleted.count
124
+
125
+ @paranoid_time_object.destroy
126
+ @paranoid_time_object.reload
127
+
128
+ assert_equal 2, ParanoidTime.not_deleted.count
129
+ assert_equal 0, ParanoidHasManyDependant.not_deleted.count
130
+ assert_equal 0, ParanoidBelongsDependant.not_deleted.count
131
+ assert_equal 0, ParanoidHasOneDependant.not_deleted.count
132
+
133
+ assert_equal 1, NotParanoid.count
134
+ assert_equal 0, HasOneNotParanoid.count
135
+ assert_equal @paranoid_boolean_count, ParanoidBoolean.not_deleted.count
136
+ end
137
+
138
+ def test_recursive_recovery
139
+ setup_recursive_recovery_tests
140
+
141
+ @paranoid_time_object.recover(:recursive => true)
142
+
143
+ assert_equal 3, ParanoidTime.not_deleted.count
144
+ assert_equal 3, ParanoidHasManyDependant.not_deleted.count
145
+ assert_equal 3, ParanoidBelongsDependant.not_deleted.count
146
+ assert_equal 3, ParanoidHasOneDependant.not_deleted.count
147
+ assert_equal 1, NotParanoid.count
148
+ assert_equal 0, HasOneNotParanoid.count
149
+ assert_equal @paranoid_boolean_count + 3, ParanoidBoolean.not_deleted.count
150
+ end
151
+
152
+ def test_non_recursive_recovery
153
+ setup_recursive_recovery_tests
154
+
155
+ @paranoid_time_object.recover(:recursive => false)
156
+
157
+ assert_equal 3, ParanoidTime.not_deleted.count
158
+ assert_equal 0, ParanoidHasManyDependant.not_deleted.count
159
+ assert_equal 0, ParanoidBelongsDependant.not_deleted.count
160
+ assert_equal 0, ParanoidHasOneDependant.not_deleted.count
161
+ assert_equal 1, NotParanoid.count
162
+ assert_equal 0, HasOneNotParanoid.count
163
+ assert_equal @paranoid_boolean_count, ParanoidBoolean.not_deleted.count
164
+ end
165
+
166
+ def test_deleted?
167
+ ParanoidTime.not_deleted.first.destroy
168
+ assert ParanoidTime.with_deleted.first.deleted?
169
+
170
+ ParanoidString.not_deleted.first.destroy
171
+ assert ParanoidString.with_deleted.first.deleted?
172
+ end
173
+
174
+ def test_paranoid_destroy_callbacks
175
+ @paranoid_with_callback = ParanoidWithCallback.not_deleted.first
176
+ ParanoidWithCallback.transaction do
177
+ @paranoid_with_callback.destroy
178
+ end
179
+
180
+ assert @paranoid_with_callback.called_before_destroy
181
+ assert @paranoid_with_callback.called_after_destroy
182
+ assert @paranoid_with_callback.called_after_commit_on_destroy
183
+ end
184
+
185
+ def test_hard_destroy_callbacks
186
+ @paranoid_with_callback = ParanoidWithCallback.not_deleted.first
187
+
188
+ ParanoidWithCallback.transaction do
189
+ @paranoid_with_callback.destroy!
190
+ end
191
+
192
+ assert @paranoid_with_callback.called_before_destroy
193
+ assert @paranoid_with_callback.called_after_destroy
194
+ assert @paranoid_with_callback.called_after_commit_on_destroy
195
+ end
196
+
197
+ def test_recovery_callbacks
198
+ @paranoid_with_callback = ParanoidWithCallback.not_deleted.first
199
+
200
+ ParanoidWithCallback.transaction do
201
+ @paranoid_with_callback.destroy
202
+
203
+ assert_nil @paranoid_with_callback.called_before_recover
204
+ assert_nil @paranoid_with_callback.called_after_recover
205
+
206
+ @paranoid_with_callback.recover
207
+ end
208
+
209
+ assert @paranoid_with_callback.called_before_recover
210
+ assert @paranoid_with_callback.called_after_recover
211
+ end
212
+ end
213
+
214
+ class ValidatesUniquenessTest < ParanoidBaseTest
215
+ def test_should_include_deleted_by_default
216
+ ParanoidTime.new(:name => 'paranoid').tap do |record|
217
+ assert !record.valid?
218
+ ParanoidTime.not_deleted.first.destroy
219
+ assert !record.valid?
220
+ ParanoidTime.only_deleted.first.destroy!
221
+ assert record.valid?
222
+ end
223
+ end
224
+
225
+ def test_should_validate_without_deleted
226
+ ParanoidBoolean.new(:name => 'paranoid').tap do |record|
227
+ ParanoidBoolean.not_deleted.first.destroy
228
+ assert record.valid?
229
+ ParanoidBoolean.only_deleted.first.destroy!
230
+ assert record.valid?
231
+ end
232
+ end
233
+ end
234
+
235
+ class AssociationsTest < ParanoidBaseTest
236
+ def test_removal_with_associations
237
+ # This test shows that the current implementation doesn't handle
238
+ # assciation deletion correctly (when hard deleting via parent-object)
239
+ paranoid_company_1 = ParanoidDestroyCompany.create! :name => "ParanoidDestroyCompany #1"
240
+ paranoid_company_2 = ParanoidDeleteCompany.create! :name => "ParanoidDestroyCompany #1"
241
+ paranoid_company_1.paranoid_products.create! :name => "ParanoidProduct #1"
242
+ paranoid_company_2.paranoid_products.create! :name => "ParanoidProduct #2"
243
+
244
+ assert_equal 1, ParanoidDestroyCompany.not_deleted.count
245
+ assert_equal 1, ParanoidDeleteCompany.not_deleted.count
246
+ assert_equal 2, ParanoidProduct.not_deleted.count
247
+
248
+ ParanoidDestroyCompany.not_deleted.first.destroy
249
+ assert_equal 0, ParanoidDestroyCompany.not_deleted.count
250
+ assert_equal 1, ParanoidProduct.not_deleted.count
251
+ assert_equal 1, ParanoidDestroyCompany.with_deleted.count
252
+ assert_equal 2, ParanoidProduct.with_deleted.count
253
+
254
+ ParanoidDestroyCompany.with_deleted.first.destroy!
255
+ assert_equal 0, ParanoidDestroyCompany.not_deleted.count
256
+ assert_equal 1, ParanoidProduct.not_deleted.count
257
+ assert_equal 0, ParanoidDestroyCompany.with_deleted.count
258
+ assert_equal 1, ParanoidProduct.with_deleted.count
259
+
260
+ ParanoidDeleteCompany.with_deleted.first.destroy!
261
+ assert_equal 0, ParanoidDeleteCompany.not_deleted.count
262
+ assert_equal 0, ParanoidProduct.not_deleted.count
263
+ assert_equal 0, ParanoidDeleteCompany.with_deleted.count
264
+ assert_equal 0, ParanoidProduct.with_deleted.count
265
+ end
266
+ end
267
+
268
+ class InheritanceTest < ParanoidBaseTest
269
+ def test_destroy_dependents_with_inheritance
270
+ has_many_inherited_super_paranoidz = HasManyInheritedSuperParanoidz.new
271
+ has_many_inherited_super_paranoidz.save
272
+ has_many_inherited_super_paranoidz.super_paranoidz.create
273
+ assert_nothing_raised(NoMethodError) { has_many_inherited_super_paranoidz.destroy }
274
+ end
275
+
276
+ def test_class_instance_variables_are_inherited
277
+ assert_nothing_raised(ActiveRecord::StatementInvalid) { InheritedParanoid.paranoid_column }
278
+ end
279
+ end
280
+
281
+ class ParanoidObserverTest < ParanoidBaseTest
282
+
283
+ def test_called_observer_methods
284
+ @subject = ParanoidWithCallback.new
285
+ @subject.save
286
+
287
+ assert_nil ParanoidObserver.instance.called_before_recover
288
+ assert_nil ParanoidObserver.instance.called_after_recover
289
+
290
+ ParanoidWithCallback.not_deleted.find(@subject.id).recover
291
+
292
+ assert_equal @subject, ParanoidObserver.instance.called_before_recover
293
+ assert_equal @subject, ParanoidObserver.instance.called_after_recover
294
+ end
295
+ end
@@ -0,0 +1,271 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
4
+ require 'active_record'
5
+ require 'active_model'
6
+
7
+ $:.unshift "#{File.dirname(__FILE__)}/../"
8
+ $:.unshift "#{File.dirname(__FILE__)}/../lib/"
9
+ $:.unshift "#{File.dirname(__FILE__)}/../lib/validations"
10
+
11
+ require 'init'
12
+
13
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
14
+
15
+ def setup_db
16
+ ActiveRecord::Schema.define(:version => 1) do
17
+ create_table :paranoid_times do |t|
18
+ t.string :name
19
+ t.datetime :deleted_at
20
+ t.integer :paranoid_belongs_dependant_id
21
+ t.integer :not_paranoid_id
22
+
23
+ t.timestamps
24
+ end
25
+
26
+ create_table :paranoid_booleans do |t|
27
+ t.string :name
28
+ t.boolean :is_deleted
29
+ t.integer :paranoid_time_id
30
+
31
+ t.timestamps
32
+ end
33
+
34
+ create_table :paranoid_strings do |t|
35
+ t.string :name
36
+ t.string :deleted
37
+ end
38
+
39
+ create_table :not_paranoids do |t|
40
+ t.string :name
41
+ t.integer :paranoid_time_id
42
+
43
+ t.timestamps
44
+ end
45
+
46
+ create_table :has_one_not_paranoids do |t|
47
+ t.string :name
48
+ t.integer :paranoid_time_id
49
+
50
+ t.timestamps
51
+ end
52
+
53
+ create_table :paranoid_has_many_dependants do |t|
54
+ t.string :name
55
+ t.datetime :deleted_at
56
+ t.integer :paranoid_time_id
57
+ t.integer :paranoid_belongs_dependant_id
58
+
59
+ t.timestamps
60
+ end
61
+
62
+ create_table :paranoid_belongs_dependants do |t|
63
+ t.string :name
64
+ t.datetime :deleted_at
65
+
66
+ t.timestamps
67
+ end
68
+
69
+ create_table :paranoid_has_one_dependants do |t|
70
+ t.string :name
71
+ t.datetime :deleted_at
72
+ t.integer :paranoid_boolean_id
73
+
74
+ t.timestamps
75
+ end
76
+
77
+ create_table :paranoid_with_callbacks do |t|
78
+ t.string :name
79
+ t.datetime :deleted_at
80
+
81
+ t.timestamps
82
+ end
83
+
84
+ create_table :paranoid_destroy_companies do |t|
85
+ t.string :name
86
+ t.datetime :deleted_at
87
+
88
+ t.timestamps
89
+ end
90
+
91
+ create_table :paranoid_delete_companies do |t|
92
+ t.string :name
93
+ t.datetime :deleted_at
94
+
95
+ t.timestamps
96
+ end
97
+
98
+ create_table :paranoid_products do |t|
99
+ t.integer :paranoid_destroy_company_id
100
+ t.integer :paranoid_delete_company_id
101
+ t.string :name
102
+ t.datetime :deleted_at
103
+
104
+ t.timestamps
105
+ end
106
+
107
+ create_table :super_paranoids do |t|
108
+ t.string :type
109
+ t.references :has_many_inherited_super_paranoidz
110
+ t.datetime :deleted_at
111
+
112
+ t.timestamps
113
+ end
114
+
115
+ create_table :has_many_inherited_super_paranoidzs do |t|
116
+ t.references :super_paranoidz
117
+ t.datetime :deleted_at
118
+
119
+ t.timestamps
120
+ end
121
+ end
122
+ end
123
+
124
+ def teardown_db
125
+ ActiveRecord::Base.connection.tables.each do |table|
126
+ ActiveRecord::Base.connection.drop_table(table)
127
+ end
128
+ end
129
+
130
+ class ParanoidTime < ActiveRecord::Base
131
+ acts_as_paranoid
132
+ validates_uniqueness_of :name
133
+
134
+ has_many :paranoid_has_many_dependants, :dependent => :destroy
135
+ has_many :paranoid_booleans, :dependent => :destroy
136
+ has_many :not_paranoids, :dependent => :delete_all
137
+
138
+ has_one :has_one_not_paranoid, :dependent => :destroy
139
+
140
+ belongs_to :not_paranoid, :dependent => :destroy
141
+ end
142
+
143
+ class ParanoidBoolean < ActiveRecord::Base
144
+ acts_as_paranoid :column_type => "boolean", :column => "is_deleted"
145
+ validates_as_paranoid
146
+
147
+ belongs_to :paranoid_time
148
+ has_one :paranoid_has_one_dependant, :dependent => :destroy
149
+ end
150
+
151
+ class ParanoidString < ActiveRecord::Base
152
+ acts_as_paranoid :column_type => "string", :column => "deleted", :deleted_value => "dead"
153
+ end
154
+
155
+ class NotParanoid < ActiveRecord::Base
156
+ end
157
+
158
+ class HasOneNotParanoid < ActiveRecord::Base
159
+ end
160
+
161
+ class ParanoidHasManyDependant < ActiveRecord::Base
162
+ acts_as_paranoid
163
+ belongs_to :paranoid_time
164
+
165
+ belongs_to :paranoid_belongs_dependant, :dependent => :destroy
166
+ end
167
+
168
+ class ParanoidBelongsDependant < ActiveRecord::Base
169
+ acts_as_paranoid
170
+
171
+ has_many :paranoid_has_many_dependants
172
+ end
173
+
174
+ class ParanoidHasOneDependant < ActiveRecord::Base
175
+ acts_as_paranoid
176
+
177
+ belongs_to :paranoid_boolean
178
+ end
179
+
180
+ class ParanoidWithCallback < ActiveRecord::Base
181
+ acts_as_paranoid
182
+
183
+ attr_accessor :called_before_destroy, :called_after_destroy, :called_after_commit_on_destroy
184
+ attr_accessor :called_before_recover, :called_after_recover
185
+
186
+ before_destroy :call_me_before_destroy
187
+ after_destroy :call_me_after_destroy
188
+
189
+ after_commit :call_me_after_commit_on_destroy, :on => :destroy
190
+
191
+ before_recover :call_me_before_recover
192
+ after_recover :call_me_after_recover
193
+
194
+ def initialize(*attrs)
195
+ @called_before_destroy = @called_after_destroy = @called_after_commit_on_destroy = false
196
+ super(*attrs)
197
+ end
198
+
199
+ def call_me_before_destroy
200
+ @called_before_destroy = true
201
+ end
202
+
203
+ def call_me_after_destroy
204
+ @called_after_destroy = true
205
+ end
206
+
207
+ def call_me_after_commit_on_destroy
208
+ @called_after_commit_on_destroy = true
209
+ end
210
+
211
+ def call_me_before_recover
212
+ @called_before_recover = true
213
+ end
214
+
215
+ def call_me_after_recover
216
+ @called_after_recover = true
217
+ end
218
+ end
219
+
220
+ class ParanoidDestroyCompany < ActiveRecord::Base
221
+ acts_as_paranoid
222
+ validates :name, :presence => true
223
+ has_many :paranoid_products, :dependent => :destroy
224
+ end
225
+
226
+ class ParanoidDeleteCompany < ActiveRecord::Base
227
+ acts_as_paranoid
228
+ validates :name, :presence => true
229
+ has_many :paranoid_products, :dependent => :delete_all
230
+ end
231
+
232
+ class ParanoidProduct < ActiveRecord::Base
233
+ acts_as_paranoid
234
+ belongs_to :paranoid_destroy_company
235
+ belongs_to :paranoid_delete_company
236
+ validates_presence_of :name
237
+ end
238
+
239
+ class SuperParanoid < ActiveRecord::Base
240
+ acts_as_paranoid
241
+ belongs_to :has_many_inherited_super_paranoidz
242
+ end
243
+
244
+ class HasManyInheritedSuperParanoidz < ActiveRecord::Base
245
+ has_many :super_paranoidz, :class_name => "InheritedParanoid", :dependent => :destroy
246
+ end
247
+
248
+ class InheritedParanoid < SuperParanoid
249
+ acts_as_paranoid
250
+ end
251
+
252
+ class ParanoidObserver < ActiveRecord::Observer
253
+ observe :paranoid_with_callback
254
+
255
+ attr_accessor :called_before_recover, :called_after_recover
256
+
257
+ def before_recover(paranoid_object)
258
+ self.called_before_recover = paranoid_object
259
+ end
260
+
261
+ def after_recover(paranoid_object)
262
+ self.called_after_recover = paranoid_object
263
+ end
264
+
265
+ def reset
266
+ self.called_before_recover = nil
267
+ self.called_after_recover = nil
268
+ end
269
+ end
270
+
271
+ ParanoidWithCallback.add_observer(ParanoidObserver.instance)
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails3_acts_as_paranoid_create
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Goncalo Silva
14
+ - Philipp Ullmann
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-09-06 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ type: :runtime
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
34
+ name: activerecord
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ prerelease: false
38
+ type: :development
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 59
45
+ segments:
46
+ - 0
47
+ - 9
48
+ - 0
49
+ version: 0.9.0
50
+ name: rake
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ prerelease: false
54
+ type: :development
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 27
61
+ segments:
62
+ - 1
63
+ - 3
64
+ - 0
65
+ version: 1.3.0
66
+ name: sqlite3-ruby
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ prerelease: false
70
+ type: :development
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 3
79
+ - 1
80
+ - 0
81
+ version: 3.1.0
82
+ name: activesupport
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ prerelease: false
86
+ type: :development
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 35
93
+ segments:
94
+ - 3
95
+ - 9
96
+ - 0
97
+ version: 3.9.0
98
+ name: rdoc
99
+ version_requirements: *id005
100
+ description: Active Record (>=3.0) plugin which allows you to hide and restore records without actually deleting them. Check its GitHub page for more in-depth information.
101
+ email:
102
+ - goncalossilva@gmail.com
103
+ - philipp.ullmann@create.at
104
+ executables: []
105
+
106
+ extensions: []
107
+
108
+ extra_rdoc_files: []
109
+
110
+ files:
111
+ - .gitignore
112
+ - Gemfile
113
+ - Gemfile.lock
114
+ - LICENSE
115
+ - README.markdown
116
+ - Rakefile
117
+ - init.rb
118
+ - lib/rails3_acts_as_paranoid.rb
119
+ - lib/rails3_acts_as_paranoid/version.rb
120
+ - lib/validations/uniqueness_without_deleted.rb
121
+ - rails3_acts_as_paranoid.gemspec
122
+ - test/rails3_acts_as_paranoid_test.rb
123
+ - test/test_helper.rb
124
+ homepage: http://github.com/goncalossilva/rails3_acts_as_paranoid
125
+ licenses: []
126
+
127
+ post_install_message:
128
+ rdoc_options: []
129
+
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ hash: 23
147
+ segments:
148
+ - 1
149
+ - 3
150
+ - 6
151
+ version: 1.3.6
152
+ requirements: []
153
+
154
+ rubyforge_project: rails3_acts_as_paranoid_create
155
+ rubygems_version: 1.8.8
156
+ signing_key:
157
+ specification_version: 3
158
+ summary: Active Record (>=3.0) plugin which allows you to hide and restore records without actually deleting them.
159
+ test_files:
160
+ - test/rails3_acts_as_paranoid_test.rb
161
+ - test/test_helper.rb