immortal 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in immortal.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
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
@@ -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