paranoid 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.textile +99 -0
- data/Rakefile +25 -0
- data/VERSION.yml +4 -0
- data/init.rb +1 -0
- data/lib/paranoid.rb +3 -0
- data/lib/paranoid/base.rb +90 -0
- data/lib/paranoid/relation.rb +49 -0
- data/paranoid.gemspec +60 -0
- data/spec/database.yml +3 -0
- data/spec/models.rb +135 -0
- data/spec/paranoid_spec.rb +99 -0
- data/spec/schema.rb +86 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +24 -0
- metadata +81 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Xspond Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
h1. paranoid
|
2
|
+
|
3
|
+
h3. advice and disclaimer
|
4
|
+
|
5
|
+
You should never expect _any_ library to work or behave exactly how you want it to: test, test, test and file an issue if you have any problems. Bonus points if you include sample failing code. Extra bonus points if you send a pull request that implements a feature/fixes a bug.
|
6
|
+
|
7
|
+
h3. How did I get here?
|
8
|
+
|
9
|
+
Sometimes you want to delete something in ActiveRecord, but you realize you might need it later (for an undo feature, or just as a safety net, etc.). There are a plethora of plugins that accomplish this, the most famous of which was the venerable acts_as_paranoid which is great but not really actively developed any more. What's more, acts_as_paranoid was written for an older version of ActiveRecord. Is_paranoid was written for ActiveRecord 2.3 and default_scope. This however became, as the author stated, a mess of hacks to catch all the edge cases. *Paranoid* is an attempt to utilize ActiveRecord::Relation and JoinDependency in ActiveRecord 3 to do all the heavy lifting without using default_scope and with_exclusive_scope.
|
10
|
+
|
11
|
+
h3. How does it work?
|
12
|
+
|
13
|
+
You should read the specs, or the RDOC, or even the source itself (which is very readable), but for the lazy, here's the hand-holding:
|
14
|
+
|
15
|
+
You need ActiveRecord 3 and you need to properly install this gem. Then you need a model with a field to serve as a flag column on its database table. For this example we'll use a timestamp named "deleted_at". If that column is null, the item isn't deleted. If it has a timestamp, it should count as deleted.
|
16
|
+
|
17
|
+
So let's assume we have a model Automobile that has a deleted_at column on the automobiles table.
|
18
|
+
|
19
|
+
If you're working with Rails, in your Gemfile, add the following (you may want to change the version number).
|
20
|
+
|
21
|
+
<pre>
|
22
|
+
gem "paranoid", :require => 'paranoid', :version => ">= 0.1.0"
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
Then in your ActiveRecord model
|
26
|
+
|
27
|
+
<pre>
|
28
|
+
class Automobile < ActiveRecord::Base
|
29
|
+
paranoid
|
30
|
+
end
|
31
|
+
</pre>
|
32
|
+
|
33
|
+
Now our automobiles are soft-deleteable.
|
34
|
+
|
35
|
+
<pre>
|
36
|
+
that_large_automobile = Automobile.create()
|
37
|
+
Automobile.count # => 1
|
38
|
+
|
39
|
+
that_large_automobile.destroy
|
40
|
+
Automobile.count # => 0
|
41
|
+
Automobile.count_with_destroyed # => 1
|
42
|
+
|
43
|
+
# where is that large automobile?
|
44
|
+
that_large_automobile = Automobile.find_with_destroyed(:all).first
|
45
|
+
that_large_automobile.restore
|
46
|
+
Automobile.count # => 1
|
47
|
+
</pre>
|
48
|
+
|
49
|
+
One thing to note, destroying is always undo-able, but deleting is not. This is a behavior difference between acts_as_paranoid and paranoid and the same as is_paranoid.
|
50
|
+
|
51
|
+
<pre>
|
52
|
+
Automobile.destroy_all
|
53
|
+
Automobile.count # => 0
|
54
|
+
Automobile.count_with_destroyed # => 1
|
55
|
+
|
56
|
+
Automobile.delete_all
|
57
|
+
Automobile.count_with_destroyed # => 0
|
58
|
+
# And you may say to yourself, "My god! What have I done?"
|
59
|
+
</pre>
|
60
|
+
|
61
|
+
Any find/count/sum/etc. _with_destroyed calls should work and you can also do find/count/sum/etc._destroyed_only.
|
62
|
+
|
63
|
+
h3. Specifying alternate rules for what should be considered destroyed
|
64
|
+
|
65
|
+
"deleted_at" as a timestamp is what acts_as_paranoid uses to define what is and isn't destroyed (see above), but you can specify alternate options with paranoid. In the paranoid line of your model you can specify the field, the value the field should have if the entry should count as destroyed, and the value the field should have if the entry is not destroyed. Consider the following models:
|
66
|
+
|
67
|
+
<pre>
|
68
|
+
class Pirate < ActiveRecord::Base
|
69
|
+
paranoid :field => [:alive, false, true]
|
70
|
+
end
|
71
|
+
|
72
|
+
class DeadPirate < ActiveRecord::Base
|
73
|
+
set_table_name :pirates
|
74
|
+
paranoid :field => [:alive, true, false]
|
75
|
+
end
|
76
|
+
</pre>
|
77
|
+
|
78
|
+
These two models share the same table, but when we are finding Pirates, we're only interested in those that are alive. To break it down, we specify :alive as our field to check, false as what the model field should be marked at when destroyed and true to what the field should be if they're not destroyed. DeadPirates are specified as the opposite. Check out the specs if you're still confused.
|
79
|
+
|
80
|
+
h3. Note about validates_uniqueness_of:
|
81
|
+
|
82
|
+
validates_uniqueness_of does not, by default, ignore items marked with a deleted_at (or other field name) flag. This is a behavior difference between paranoid and acts_as_paranoid and the same as is_paranoid. You can overcome this by specifying the field name you are using to mark destroyed items as your scope. Example:
|
83
|
+
|
84
|
+
<pre>
|
85
|
+
class Android < ActiveRecord::Base
|
86
|
+
validates_uniqueness_of :name, :scope => :deleted_at
|
87
|
+
paranoid
|
88
|
+
end
|
89
|
+
</pre>
|
90
|
+
|
91
|
+
And now the validates_uniqueness_of will ignore items that are destroyed.
|
92
|
+
|
93
|
+
h3. and you may ask yourself, where does that highway go to?
|
94
|
+
|
95
|
+
If you find any bugs, have any ideas of features you think are missing, or find things you're like to see work differently, feel free to file an issue or send a pull request.
|
96
|
+
|
97
|
+
h3. Thanks
|
98
|
+
|
99
|
+
Thanks to Rick Olson for acts_as_paranoid and to Jeffrey Chupp for is_paranoid.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "spec"
|
2
|
+
require "spec/rake/spectask"
|
3
|
+
|
4
|
+
Spec::Rake::SpecTask.new do |t|
|
5
|
+
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
6
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |s|
|
12
|
+
s.name = %q{paranoid}
|
13
|
+
s.summary = %q{Enable soft delete of ActiveRecord records. Based off defunct ActsAsParanoid and IsParanoid}
|
14
|
+
s.email = %q{github@xspond.com}
|
15
|
+
s.homepage = %q{http://github.com/xspond/paranoid/}
|
16
|
+
s.description = ""
|
17
|
+
s.authors = ["David Genord II"]
|
18
|
+
s.add_dependency('activerecord', '~> 3.0.0.beta')
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => :spec
|
data/VERSION.yml
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'paranoid'
|
data/lib/paranoid.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Paranoid
|
2
|
+
module Base
|
3
|
+
def paranoid(opts = {})
|
4
|
+
return if paranoid?
|
5
|
+
@paranoid = true
|
6
|
+
|
7
|
+
opts[:field] ||= [:deleted_at, Proc.new{Time.now.utc}, nil]
|
8
|
+
class_inheritable_accessor :destroyed_field, :field_destroyed, :field_not_destroyed
|
9
|
+
self.destroyed_field, self.field_destroyed, self.field_not_destroyed = opts[:field]
|
10
|
+
|
11
|
+
extend ClassMethods
|
12
|
+
include InstanceMethods
|
13
|
+
|
14
|
+
class_eval do
|
15
|
+
class << self
|
16
|
+
delegate :with_destroyed, :to => :scoped
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def paranoid?
|
22
|
+
@paranoid = false unless defined?(@paranoid)
|
23
|
+
@paranoid
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def paranoid_condition
|
28
|
+
{destroyed_field => field_not_destroyed}
|
29
|
+
end
|
30
|
+
|
31
|
+
def paranoid_only_condition
|
32
|
+
["#{table_name}.#{destroyed_field} IS NOT ?", field_not_destroyed]
|
33
|
+
end
|
34
|
+
|
35
|
+
def disable_paranoid
|
36
|
+
if block_given?
|
37
|
+
@paranoid = false
|
38
|
+
yield
|
39
|
+
end
|
40
|
+
ensure
|
41
|
+
@paranoid = true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceMethods
|
46
|
+
extend ActiveSupport::Concern
|
47
|
+
|
48
|
+
included do
|
49
|
+
alias_method_chain :create_or_update, :paranoid
|
50
|
+
end
|
51
|
+
|
52
|
+
def restore
|
53
|
+
set_destroyed(field_not_destroyed.respond_to?(:call) ? field_not_destroyed.call : field_not_destroyed)
|
54
|
+
@destroyed = false
|
55
|
+
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Override the default destroy to allow us to soft delete records.
|
60
|
+
# This preserves the before_destroy and after_destroy callbacks.
|
61
|
+
# Because this is also called internally by Model.destroy_all and
|
62
|
+
# the Model.destroy(id), we don't need to specify those methods
|
63
|
+
# separately.
|
64
|
+
def destroy
|
65
|
+
_run_destroy_callbacks do
|
66
|
+
set_destroyed(field_destroyed.respond_to?(:call) ? field_destroyed.call : field_destroyed)
|
67
|
+
@destroyed = true
|
68
|
+
end
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def create_or_update_with_paranoid
|
76
|
+
self.class.disable_paranoid { create_or_update_without_paranoid }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set the value for the destroyed field.
|
80
|
+
def set_destroyed(val)
|
81
|
+
self[destroyed_field] = val
|
82
|
+
updates = self.class.send(:sanitize_sql_for_assignment, {destroyed_field => val})
|
83
|
+
self.class.unscoped.with_destroyed.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(updates)
|
84
|
+
@destroyed = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
ActiveRecord::Base.class_eval { extend Paranoid::Base }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Paranoid
|
2
|
+
module Relation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :arel, :paranoid
|
7
|
+
alias_method_chain :delete_all, :paranoid
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_paranoid_condition?
|
11
|
+
@add_paranoid = true unless defined?(@add_paranoid)
|
12
|
+
@klass.paranoid? && @add_paranoid
|
13
|
+
end
|
14
|
+
|
15
|
+
def arel_with_paranoid
|
16
|
+
if add_paranoid_condition?
|
17
|
+
@arel ||= without_destroyed.arel_without_paranoid
|
18
|
+
else
|
19
|
+
arel_without_paranoid
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_all_with_paranoid(*args)
|
24
|
+
if add_paranoid_condition?
|
25
|
+
with_destroyed.delete_all_without_paranoid(*args)
|
26
|
+
else
|
27
|
+
delete_all_without_paranoid(*args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def skip_paranoid_condition
|
32
|
+
@add_paranoid = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_destroyed
|
36
|
+
spawn.tap {|relation| relation.skip_paranoid_condition }
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_destroyed_only
|
40
|
+
where(@klass.paranoid_only_condition).tap {|relation| relation.skip_paranoid_condition }
|
41
|
+
end
|
42
|
+
|
43
|
+
def without_destroyed
|
44
|
+
where(@klass.paranoid_condition).tap {|relation| relation.skip_paranoid_condition }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ActiveRecord::Relation.class_eval { include Paranoid::Relation }
|
data/paranoid.gemspec
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{paranoid}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["David Genord II"]
|
12
|
+
s.date = %q{2010-02-18}
|
13
|
+
s.description = %q{}
|
14
|
+
s.email = %q{github@xspond.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.textile"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"MIT-LICENSE",
|
20
|
+
"README.textile",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"init.rb",
|
24
|
+
"lib/paranoid.rb",
|
25
|
+
"lib/paranoid/base.rb",
|
26
|
+
"lib/paranoid/relation.rb",
|
27
|
+
"paranoid.gemspec",
|
28
|
+
"spec/database.yml",
|
29
|
+
"spec/models.rb",
|
30
|
+
"spec/paranoid_spec.rb",
|
31
|
+
"spec/schema.rb",
|
32
|
+
"spec/spec.opts",
|
33
|
+
"spec/spec_helper.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/xspond/paranoid/}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.5}
|
39
|
+
s.summary = %q{Enable soft delete of ActiveRecord records. Based off defunct ActsAsParanoid and IsParanoid}
|
40
|
+
s.test_files = [
|
41
|
+
"spec/models.rb",
|
42
|
+
"spec/paranoid_spec.rb",
|
43
|
+
"spec/schema.rb",
|
44
|
+
"spec/spec_helper.rb"
|
45
|
+
]
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.0.beta"])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/spec/database.yml
ADDED
data/spec/models.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
class Person < ActiveRecord::Base #:nodoc:
|
2
|
+
validates_uniqueness_of :name
|
3
|
+
has_many :androids, :foreign_key => :owner_id, :dependent => :destroy
|
4
|
+
end
|
5
|
+
|
6
|
+
class Android < ActiveRecord::Base #:nodoc:
|
7
|
+
paranoid
|
8
|
+
validates_uniqueness_of :name
|
9
|
+
has_many :components, :dependent => :destroy
|
10
|
+
has_one :sticker
|
11
|
+
has_many :memories, :foreign_key => 'parent_id'
|
12
|
+
has_many :dents
|
13
|
+
has_many :dings, :through => :dents
|
14
|
+
has_many :scratches, :through => :dents
|
15
|
+
has_and_belongs_to_many :places
|
16
|
+
|
17
|
+
# this code is to ensure that our destroy and restore methods
|
18
|
+
# work without triggering before/after_update callbacks
|
19
|
+
before_update :raise_hell
|
20
|
+
def raise_hell
|
21
|
+
raise "hell"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Dent < ActiveRecord::Base #:nodoc:
|
26
|
+
paranoid
|
27
|
+
belongs_to :android
|
28
|
+
has_many :dings
|
29
|
+
has_many :scratches
|
30
|
+
end
|
31
|
+
|
32
|
+
class Ding < ActiveRecord::Base #:nodoc:
|
33
|
+
paranoid :field => [:not_deleted, true, false]
|
34
|
+
belongs_to :dent
|
35
|
+
end
|
36
|
+
|
37
|
+
class Scratch < ActiveRecord::Base #:nodoc:
|
38
|
+
paranoid
|
39
|
+
belongs_to :dent
|
40
|
+
end
|
41
|
+
|
42
|
+
class Component < ActiveRecord::Base #:nodoc:
|
43
|
+
paranoid
|
44
|
+
belongs_to :android, :dependent => :destroy
|
45
|
+
has_many :sub_components, :dependent => :destroy
|
46
|
+
NEW_NAME = 'Something Else!'
|
47
|
+
|
48
|
+
after_destroy :change_name
|
49
|
+
def change_name
|
50
|
+
self.update_attribute(:name, NEW_NAME)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class SubComponent < ActiveRecord::Base #:nodoc:
|
55
|
+
paranoid
|
56
|
+
belongs_to :component, :dependent => :destroy
|
57
|
+
end
|
58
|
+
|
59
|
+
class Memory < ActiveRecord::Base #:nodoc:
|
60
|
+
paranoid
|
61
|
+
belongs_to :android, :class_name => "Android", :foreign_key => "parent_id"
|
62
|
+
end
|
63
|
+
|
64
|
+
class Sticker < ActiveRecord::Base #:nodoc:
|
65
|
+
MM_NAME = "You've got method_missing"
|
66
|
+
|
67
|
+
# this simply serves to ensure that we don't break method_missing
|
68
|
+
# if it is implemented on a class and called before is_paranoid
|
69
|
+
def method_missing name, *args, &block
|
70
|
+
self.name = MM_NAME
|
71
|
+
end
|
72
|
+
|
73
|
+
paranoid
|
74
|
+
belongs_to :android
|
75
|
+
end
|
76
|
+
|
77
|
+
class AndroidWithScopedUniqueness < ActiveRecord::Base #:nodoc:
|
78
|
+
set_table_name :androids
|
79
|
+
validates_uniqueness_of :name, :scope => :deleted_at
|
80
|
+
paranoid
|
81
|
+
end
|
82
|
+
|
83
|
+
class Place < ActiveRecord::Base #:nodoc:
|
84
|
+
paranoid
|
85
|
+
has_and_belongs_to_many :androids
|
86
|
+
end
|
87
|
+
|
88
|
+
class AndroidsPlaces < ActiveRecord::Base #:nodoc:
|
89
|
+
end
|
90
|
+
|
91
|
+
class Ninja < ActiveRecord::Base #:nodoc:
|
92
|
+
validates_uniqueness_of :name, :scope => :visible
|
93
|
+
paranoid :field => [:visible, false, true]
|
94
|
+
|
95
|
+
alias_method :vanish, :destroy
|
96
|
+
end
|
97
|
+
|
98
|
+
class Pirate < ActiveRecord::Base #:nodoc:
|
99
|
+
paranoid :field => [:alive, false, true]
|
100
|
+
end
|
101
|
+
|
102
|
+
class DeadPirate < ActiveRecord::Base #:nodoc:
|
103
|
+
set_table_name :pirates
|
104
|
+
paranoid :field => [:alive, true, false]
|
105
|
+
end
|
106
|
+
|
107
|
+
class RandomPirate < ActiveRecord::Base #:nodoc:
|
108
|
+
set_table_name :pirates
|
109
|
+
|
110
|
+
after_destroy :raise_an_error
|
111
|
+
def raise_an_error
|
112
|
+
raise 'after_destroy works'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class UndestroyablePirate < ActiveRecord::Base #:nodoc:
|
117
|
+
set_table_name :pirates
|
118
|
+
paranoid :field => [:alive, false, true]
|
119
|
+
|
120
|
+
before_destroy :ret_false
|
121
|
+
def ret_false
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Uuid < ActiveRecord::Base #:nodoc:
|
127
|
+
set_primary_key "uuid"
|
128
|
+
|
129
|
+
before_create :set_uuid
|
130
|
+
def set_uuid
|
131
|
+
self.uuid = "295b3430-85b8-012c-cfe4-002332cf7d5e"
|
132
|
+
end
|
133
|
+
|
134
|
+
paranoid
|
135
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/models')
|
3
|
+
|
4
|
+
LUKE = 'Luke Skywalker'
|
5
|
+
|
6
|
+
describe Paranoid do
|
7
|
+
before(:each) do
|
8
|
+
Sticker.delete_all
|
9
|
+
Place.delete_all
|
10
|
+
Android.delete_all
|
11
|
+
Person.delete_all
|
12
|
+
Component.delete_all
|
13
|
+
|
14
|
+
@luke = Person.create(:name => LUKE)
|
15
|
+
@r2d2 = Android.create(:name => 'R2D2', :owner_id => @luke.id)
|
16
|
+
@c3p0 = Android.create(:name => 'C3P0', :owner_id => @luke.id)
|
17
|
+
|
18
|
+
@r2d2.components.create(:name => 'Rotors')
|
19
|
+
|
20
|
+
@r2d2.memories.create(:name => 'A pretty sunset')
|
21
|
+
@c3p0.sticker = Sticker.create(:name => 'OMG, PONIES!')
|
22
|
+
@tatooine = Place.create(:name => "Tatooine")
|
23
|
+
@r2d2.places << @tatooine
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should recognize a class as paranoid' do
|
27
|
+
Person.paranoid?.should be_false
|
28
|
+
Place.paranoid?.should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should hide destroyed records' do
|
32
|
+
@tatooine.update_attribute('deleted_at', Time.now)
|
33
|
+
Place.first(:conditions => {:name => 'Tatooine'}).should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should reveal destroyed records when with_destroyed' do
|
37
|
+
@tatooine.update_attribute('deleted_at', Time.now)
|
38
|
+
Place.with_destroyed.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should restore the destroyed record' do
|
42
|
+
@tatooine.update_attribute('deleted_at', Time.now)
|
43
|
+
|
44
|
+
@tatooine = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
|
45
|
+
@tatooine.restore
|
46
|
+
|
47
|
+
Place.first(:conditions => {:name => 'Tatooine'}).should_not be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should soft delete paranoid records' do
|
51
|
+
@tatooine.destroy
|
52
|
+
|
53
|
+
record = Place.with_destroyed.first(:conditions => {:name => 'Tatooine'})
|
54
|
+
record.should_not be_nil
|
55
|
+
record.deleted_at.should_not be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should mark the record destroyed' do
|
59
|
+
@tatooine.destroy
|
60
|
+
@tatooine.destroyed?.should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should set the deleted_field' do
|
64
|
+
@tatooine.destroy
|
65
|
+
@tatooine.deleted_at.should_not be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'for alternate field information' do
|
69
|
+
before(:each) do
|
70
|
+
Ninja.delete_all
|
71
|
+
@ninja = Ninja.create(:name => 'Steve')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should vanish the ninja' do
|
75
|
+
@ninja.destroy
|
76
|
+
|
77
|
+
record = Ninja.first(:conditions => {:name => 'Steve'})
|
78
|
+
record.should be_nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should not delete the ninja' do
|
82
|
+
@ninja.destroy
|
83
|
+
|
84
|
+
record = Ninja.with_destroyed.first(:conditions => {:name => 'Steve'})
|
85
|
+
record.should_not be_nil
|
86
|
+
record.visible.should be_false
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should mark the ninja vanished' do
|
90
|
+
@ninja.destroy
|
91
|
+
@ninja.destroyed?.should be_true
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should set visible to false' do
|
95
|
+
@ninja.destroy
|
96
|
+
@ninja.visible.should be_false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table "androids", :force => true do |t|
|
3
|
+
t.string "name"
|
4
|
+
t.integer "owner_id"
|
5
|
+
t.datetime "deleted_at"
|
6
|
+
t.datetime "created_at"
|
7
|
+
t.datetime "updated_at"
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table "dents", :force => true do |t|
|
11
|
+
t.integer "android_id"
|
12
|
+
t.string "description"
|
13
|
+
t.datetime "deleted_at"
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table "dings", :force => true do |t|
|
17
|
+
t.integer "dent_id"
|
18
|
+
t.string "description"
|
19
|
+
t.boolean "not_deleted"
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table "scratches", :force => true do |t|
|
23
|
+
t.integer "dent_id"
|
24
|
+
t.string "description"
|
25
|
+
t.datetime "deleted_at"
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table "androids_places", :force => true, :id => false do |t|
|
29
|
+
t.integer "android_id"
|
30
|
+
t.integer "place_id"
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table "places", :force => true do |t|
|
34
|
+
t.string "name"
|
35
|
+
t.datetime "deleted_at"
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table "people", :force => true do |t|
|
39
|
+
t.string "name"
|
40
|
+
t.datetime "created_at"
|
41
|
+
t.datetime "updated_at"
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table "components", :force => true do |t|
|
45
|
+
t.string "name"
|
46
|
+
t.integer "android_id"
|
47
|
+
t.datetime "deleted_at"
|
48
|
+
t.datetime "created_at"
|
49
|
+
t.datetime "updated_at"
|
50
|
+
end
|
51
|
+
|
52
|
+
create_table "sub_components", :force => true do |t|
|
53
|
+
t.string "name"
|
54
|
+
t.integer "component_id"
|
55
|
+
t.datetime "deleted_at"
|
56
|
+
end
|
57
|
+
|
58
|
+
create_table "memories", :force => true do |t|
|
59
|
+
t.string "name"
|
60
|
+
t.integer "parent_id"
|
61
|
+
t.datetime "deleted_at"
|
62
|
+
end
|
63
|
+
|
64
|
+
create_table "stickers", :force => true do |t|
|
65
|
+
t.string "name"
|
66
|
+
t.integer "android_id"
|
67
|
+
t.datetime "deleted_at"
|
68
|
+
end
|
69
|
+
|
70
|
+
create_table "ninjas", :force => true do |t|
|
71
|
+
t.string "name"
|
72
|
+
t.boolean "visible", :default => false
|
73
|
+
end
|
74
|
+
|
75
|
+
create_table "pirates", :force => true do |t|
|
76
|
+
t.string "name"
|
77
|
+
t.boolean "alive", :default => true
|
78
|
+
end
|
79
|
+
|
80
|
+
create_table "uuids", :id => false, :force => true do |t|
|
81
|
+
t.string "uuid", :limit => 36, :primary => true
|
82
|
+
t.string "name"
|
83
|
+
t.datetime "deleted_at"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'active_record'
|
5
|
+
require 'paranoid'
|
6
|
+
require 'yaml'
|
7
|
+
require 'spec'
|
8
|
+
|
9
|
+
def connect(environment)
|
10
|
+
conf = YAML::load(File.open(File.dirname(__FILE__) + '/database.yml'))
|
11
|
+
ActiveRecord::Base.establish_connection(conf[environment])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Open ActiveRecord connection
|
15
|
+
connect('test')
|
16
|
+
|
17
|
+
original_stdout = $stdout
|
18
|
+
$stdout = StringIO.new
|
19
|
+
|
20
|
+
begin
|
21
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
22
|
+
ensure
|
23
|
+
$stdout = original_stdout
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: paranoid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Genord II
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-18 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activerecord
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.0.0.beta
|
24
|
+
version:
|
25
|
+
description: ""
|
26
|
+
email: github@xspond.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.textile
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.textile
|
36
|
+
- Rakefile
|
37
|
+
- VERSION.yml
|
38
|
+
- init.rb
|
39
|
+
- lib/paranoid.rb
|
40
|
+
- lib/paranoid/base.rb
|
41
|
+
- lib/paranoid/relation.rb
|
42
|
+
- paranoid.gemspec
|
43
|
+
- spec/database.yml
|
44
|
+
- spec/models.rb
|
45
|
+
- spec/paranoid_spec.rb
|
46
|
+
- spec/schema.rb
|
47
|
+
- spec/spec.opts
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/xspond/paranoid/
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Enable soft delete of ActiveRecord records. Based off defunct ActsAsParanoid and IsParanoid
|
77
|
+
test_files:
|
78
|
+
- spec/models.rb
|
79
|
+
- spec/paranoid_spec.rb
|
80
|
+
- spec/schema.rb
|
81
|
+
- spec/spec_helper.rb
|