immortal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/README.md +43 -0
- data/Rakefile +2 -0
- data/immortal.gemspec +22 -0
- data/lib/immortal.rb +80 -0
- data/spec/immortal_spec.rb +180 -0
- data/spec/spec_helper.rb +62 -0
- metadata +126 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
immortal (0.1.0)
|
5
|
+
activerecord (~> 3.0.3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (3.0.3)
|
11
|
+
activesupport (= 3.0.3)
|
12
|
+
builder (~> 2.1.2)
|
13
|
+
i18n (~> 0.4)
|
14
|
+
activerecord (3.0.3)
|
15
|
+
activemodel (= 3.0.3)
|
16
|
+
activesupport (= 3.0.3)
|
17
|
+
arel (~> 2.0.2)
|
18
|
+
tzinfo (~> 0.3.23)
|
19
|
+
activesupport (3.0.3)
|
20
|
+
arel (2.0.6)
|
21
|
+
builder (2.1.2)
|
22
|
+
diff-lcs (1.1.2)
|
23
|
+
i18n (0.5.0)
|
24
|
+
rspec (2.3.0)
|
25
|
+
rspec-core (~> 2.3.0)
|
26
|
+
rspec-expectations (~> 2.3.0)
|
27
|
+
rspec-mocks (~> 2.3.0)
|
28
|
+
rspec-core (2.3.1)
|
29
|
+
rspec-expectations (2.3.0)
|
30
|
+
diff-lcs (~> 1.1.2)
|
31
|
+
rspec-mocks (2.3.0)
|
32
|
+
sqlite3-ruby (1.3.2)
|
33
|
+
tzinfo (0.3.23)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
activerecord (~> 3.0.3)
|
40
|
+
immortal!
|
41
|
+
rspec (~> 2.3.0)
|
42
|
+
sqlite3-ruby (~> 1.3.2)
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Immortal
|
2
|
+
|
3
|
+
Make any ActiveRecord model paranoid by just including `Immortal`, and instead of being deleted from the database, the object will just marked as 'deleted' with a boolean field in the database.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem dependency to your Gemfile:
|
8
|
+
|
9
|
+
gem 'immortal'
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
class User < ActiveRecord::Base
|
14
|
+
include Immortal
|
15
|
+
end
|
16
|
+
|
17
|
+
And add a boolean field called `deleted` to that model:
|
18
|
+
|
19
|
+
class AddDeletedToUsers < ActiveRecord::Migration
|
20
|
+
def self.up
|
21
|
+
add_column :users, :deleted, :boolean
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
remove_column :users, :deleted
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
## TODO
|
30
|
+
|
31
|
+
- Add documentation in the code
|
32
|
+
- Spec associations
|
33
|
+
- Add support for a :with_deleted option in associations, like acts_as_paranoid
|
34
|
+
- Improve compatibility with acts_as_paranoid
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
If you want to improve immortal
|
39
|
+
|
40
|
+
1. Fork the repo
|
41
|
+
2. Create a topic branch `git checkout -b my_feature`
|
42
|
+
3. Push it! `git push origin my_feature`
|
43
|
+
4. Open a pull request
|
data/Rakefile
ADDED
data/immortal.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "immortal"
|
6
|
+
s.version = '0.1.0'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Jordi Romero", "Saimon Moore"]
|
9
|
+
s.email = ["jordi@jrom.net", "saimon@saimonmoore.net"]
|
10
|
+
s.homepage = "http://github.com/teambox/immortal"
|
11
|
+
s.summary = %q{Replacement for acts_as_paranoid for Rails 3}
|
12
|
+
s.description = %q{Typical paranoid gem built for Rails 3 and with the minimum code needed to satisfy acts_as_paranoid's API}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'activerecord', '~> 3.0.3'
|
20
|
+
s.add_development_dependency 'rspec', '~> 2.3.0'
|
21
|
+
s.add_development_dependency 'sqlite3-ruby', '~> 1.3.2'
|
22
|
+
end
|
data/lib/immortal.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Immortal
|
2
|
+
def self.included(base)
|
3
|
+
base.send :extend, ClassMethods
|
4
|
+
base.send :include, InstanceMethods
|
5
|
+
base.class_eval do
|
6
|
+
class << self
|
7
|
+
alias :mortal_delete_all :delete_all
|
8
|
+
alias :delete_all :immortal_delete_all
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
def with_deleted
|
16
|
+
unscoped
|
17
|
+
end
|
18
|
+
|
19
|
+
def only_deleted
|
20
|
+
unscoped.where(:deleted => true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def count_with_deleted(*args)
|
24
|
+
with_deleted.count(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def count_only_deleted(*args)
|
28
|
+
only_deleted.count(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_with_deleted(*args)
|
32
|
+
with_deleted.find(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_only_deleted(*args)
|
36
|
+
only_deleted.find(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def immortal_delete_all(*args)
|
40
|
+
unscoped.update_all :deleted => true
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_all!(*args)
|
44
|
+
unscoped.mortal_delete_all
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
def self.included(base)
|
51
|
+
base.class_eval do
|
52
|
+
default_scope where(arel_table[:deleted].eq(nil).or(arel_table[:deleted].eq(false)))
|
53
|
+
alias :mortal_destroy :destroy
|
54
|
+
alias :destroy :immortal_destroy
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def immortal_destroy(*args)
|
59
|
+
run_callbacks :destroy do
|
60
|
+
destroy_without_callbacks(*args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy!(*args)
|
65
|
+
mortal_destroy
|
66
|
+
end
|
67
|
+
|
68
|
+
def destroy_without_callbacks(*args)
|
69
|
+
self.class.unscoped.update_all({ :deleted => true }, "id = #{self.id}")
|
70
|
+
reload
|
71
|
+
freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
def recover!
|
75
|
+
self.class.unscoped.update_all({ :deleted => false }, "id = #{self.id}")
|
76
|
+
reload
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Immortal do
|
4
|
+
before do
|
5
|
+
@m = ImmortalModel.create! :title => 'testing immortal'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should not be deleted from the database using #destroy" do
|
9
|
+
expect {
|
10
|
+
@m.destroy
|
11
|
+
}.to_not change(ImmortalModel, :count_with_deleted)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be frozen using #destroy" do
|
15
|
+
@m.destroy
|
16
|
+
@m.should be_frozen
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not be dirty using #destroy" do
|
20
|
+
@m.destroy
|
21
|
+
@m.should_not be_changed
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be deleted from the database using #destroy!" do
|
25
|
+
expect {
|
26
|
+
@m.destroy!
|
27
|
+
}.to change(ImmortalModel, :count_with_deleted)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should find non deleted records" do
|
31
|
+
ImmortalModel.first.should == @m
|
32
|
+
ImmortalModel.all.should include(@m)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should not find deleted records" do
|
36
|
+
@m.destroy
|
37
|
+
ImmortalModel.first.should be_nil
|
38
|
+
ImmortalModel.all.should be_empty
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should find deleted records using scope" do
|
42
|
+
@m.destroy
|
43
|
+
ImmortalModel.with_deleted.first.should == @m
|
44
|
+
ImmortalModel.with_deleted.all.should include(@m)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should find deleted records using the old method" do
|
48
|
+
ImmortalModel.find_with_deleted(@m.id).should == @m
|
49
|
+
@m.destroy
|
50
|
+
ImmortalModel.find_with_deleted(@m.id).should == @m
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should count undeleted records by default" do
|
54
|
+
@m2 = ImmortalModel.create! :title => 'testing immortal again'
|
55
|
+
ImmortalModel.count_only_deleted.should == 0
|
56
|
+
ImmortalModel.only_deleted.count.should == 0
|
57
|
+
|
58
|
+
@m.destroy
|
59
|
+
|
60
|
+
ImmortalModel.count_only_deleted.should == 1
|
61
|
+
ImmortalModel.only_deleted.count.should == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should find only deleted records" do
|
65
|
+
@m2 = ImmortalModel.create! :title => 'testing immortal again'
|
66
|
+
expect {
|
67
|
+
ImmortalModel.find_only_deleted(@m.id)
|
68
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
69
|
+
|
70
|
+
expect {
|
71
|
+
ImmortalModel.only_deleted.find(@m.id)
|
72
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
73
|
+
|
74
|
+
@m.destroy
|
75
|
+
|
76
|
+
ImmortalModel.find_only_deleted(@m.id).should == @m
|
77
|
+
expect {
|
78
|
+
ImmortalModel.find_only_deleted(@m2.id)
|
79
|
+
}.to raise_error(ActiveRecord::RecordNotFound)
|
80
|
+
|
81
|
+
ImmortalModel.only_deleted.should include(@m)
|
82
|
+
ImmortalModel.only_deleted.should_not include(@m2)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should be able to count undeleted records" do
|
86
|
+
@m2 = ImmortalModel.create! :title => 'testing immortal again'
|
87
|
+
ImmortalModel.count.should == 2
|
88
|
+
|
89
|
+
@m.destroy
|
90
|
+
|
91
|
+
ImmortalModel.count.should == 1
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be able to count all the records including deleted" do
|
95
|
+
@m2 = ImmortalModel.create! :title => 'testing immortal again'
|
96
|
+
@m.destroy
|
97
|
+
ImmortalModel.count_with_deleted.should == 2
|
98
|
+
ImmortalModel.with_deleted.count.should == 2
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should not exist if deleted" do
|
102
|
+
ImmortalModel.exists?(@m.id).should be_true
|
103
|
+
@m.destroy
|
104
|
+
ImmortalModel.exists?(@m.id).should be_false
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should calculate without deleted" do
|
108
|
+
@m2 = ImmortalModel.create! :value => 10
|
109
|
+
@m3 = ImmortalModel.create! :value => 20
|
110
|
+
ImmortalModel.calculate(:sum, :value).should == 30
|
111
|
+
@m2.destroy
|
112
|
+
ImmortalModel.calculate(:sum, :value).should == 20
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should execute the before_destroy callback when immortally destroyed" do
|
116
|
+
@m.destroy
|
117
|
+
@m.before_d.should be_true
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should execute the after_destroy callback when immortally destroyed" do
|
121
|
+
@m.destroy
|
122
|
+
@m.after_d.should be_true
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should not execute the before_update callback when immortally destroyed" do
|
126
|
+
@m.destroy
|
127
|
+
@m.before_u.should be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should not execute the after_update callback when immortally destroyed" do
|
131
|
+
@m.destroy
|
132
|
+
@m.after_u.should be_nil
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not execute the before_destroy callback when immortally destroyed without callbacks" do
|
136
|
+
@m.destroy_without_callbacks
|
137
|
+
@m.before_d.should be_nil
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not execute the after_destroy callback when immortally destroyed without callbacks" do
|
141
|
+
@m.destroy_without_callbacks
|
142
|
+
@m.after_d.should be_nil
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should immortally delete all records with delete_all" do
|
146
|
+
expect {
|
147
|
+
ImmortalModel.delete_all
|
148
|
+
}.to change(ImmortalModel, :count).by(-1)
|
149
|
+
ImmortalModel.count_with_deleted.should == 1
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should immortally delete all records with delete_all!" do
|
153
|
+
expect {
|
154
|
+
ImmortalModel.delete_all!
|
155
|
+
}.to change(ImmortalModel.with_deleted, :count).by(-1)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should know if it's deleted" do
|
159
|
+
@m.should_not be_deleted
|
160
|
+
@m.destroy
|
161
|
+
@m.should be_deleted
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should be recoverable" do
|
165
|
+
@m.destroy
|
166
|
+
@m = ImmortalModel.with_deleted.find(@m.id)
|
167
|
+
@m.recover!
|
168
|
+
@m.should_not be_frozen
|
169
|
+
@m.should_not be_changed
|
170
|
+
ImmortalModel.first.should == @m
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should consider an object with deleted = nil as not deleted" do
|
174
|
+
@m2 = ImmortalModel.create! :deleted => nil
|
175
|
+
@m2.deleted.should be_nil
|
176
|
+
@m2.should_not be_deleted
|
177
|
+
ImmortalModel.count.should == 2
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Bundler.require(:default, :development)
|
2
|
+
require 'rspec'
|
3
|
+
require 'lib/immortal'
|
4
|
+
require 'active_record'
|
5
|
+
require 'sqlite3'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.before(:each) do
|
9
|
+
ActiveRecord::Base.connection.execute('delete from immortal_models')
|
10
|
+
end
|
11
|
+
|
12
|
+
config.after(:all) do
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
18
|
+
|
19
|
+
old_stdout = $stdout
|
20
|
+
$stdout = StringIO.new
|
21
|
+
|
22
|
+
begin
|
23
|
+
ActiveRecord::Schema.define do
|
24
|
+
create_table :immortal_models do |t|
|
25
|
+
t.string :title
|
26
|
+
t.integer :value
|
27
|
+
t.boolean :deleted, :default => false
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
$stdout = old_stdout
|
33
|
+
end
|
34
|
+
|
35
|
+
class ImmortalModel < ActiveRecord::Base
|
36
|
+
include Immortal
|
37
|
+
|
38
|
+
attr_accessor :before_d, :after_d, :before_u, :after_u
|
39
|
+
|
40
|
+
before_destroy :set_before
|
41
|
+
after_destroy :set_after
|
42
|
+
before_update :set_before_update
|
43
|
+
after_update :set_after_update
|
44
|
+
|
45
|
+
private
|
46
|
+
def set_before
|
47
|
+
@before_d = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_after
|
51
|
+
@after_d = true
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_after_update
|
55
|
+
@after_u = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_before_update
|
59
|
+
@before_u = true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: immortal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jordi Romero
|
14
|
+
- Saimon Moore
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-12-17 00:00:00 +01:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: activerecord
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 1
|
31
|
+
segments:
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
- 3
|
35
|
+
version: 3.0.3
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 2
|
49
|
+
- 3
|
50
|
+
- 0
|
51
|
+
version: 2.3.0
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: sqlite3-ruby
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 31
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 3
|
66
|
+
- 2
|
67
|
+
version: 1.3.2
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
description: Typical paranoid gem built for Rails 3 and with the minimum code needed to satisfy acts_as_paranoid's API
|
71
|
+
email:
|
72
|
+
- jordi@jrom.net
|
73
|
+
- saimon@saimonmoore.net
|
74
|
+
executables: []
|
75
|
+
|
76
|
+
extensions: []
|
77
|
+
|
78
|
+
extra_rdoc_files: []
|
79
|
+
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- Gemfile
|
83
|
+
- Gemfile.lock
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- immortal.gemspec
|
87
|
+
- lib/immortal.rb
|
88
|
+
- spec/immortal_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
has_rdoc: true
|
91
|
+
homepage: http://github.com/teambox/immortal
|
92
|
+
licenses: []
|
93
|
+
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
requirements: []
|
118
|
+
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.3.7
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Replacement for acts_as_paranoid for Rails 3
|
124
|
+
test_files:
|
125
|
+
- spec/immortal_spec.rb
|
126
|
+
- spec/spec_helper.rb
|