delete_softly 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +13 -1
- data/Rakefile +1 -1
- data/delete_softly.gemspec +2 -2
- data/lib/class_methods.rb +28 -8
- data/lib/delete_softly.rb +43 -9
- data/lib/instance_methods.rb +17 -2
- data/test/delete_softly_test.rb +34 -0
- data/test/test_helper.rb +54 -0
- metadata +4 -4
data/README
CHANGED
@@ -2,19 +2,30 @@ DeleteSoftly
|
|
2
2
|
============
|
3
3
|
|
4
4
|
Add soft delete functionality to ActiveRecord models. Important information:
|
5
|
-
This is Rails3 only, no backwards compatibility.
|
5
|
+
This is Rails3 only, no backwards compatibility. Important features are
|
6
|
+
* It works through relations
|
7
|
+
* papertrail support
|
8
|
+
|
9
|
+
Tested with Postgresql
|
10
|
+
|
11
|
+
New in version 0.3
|
12
|
+
* without_deleted, same as active, but not meant to be overwritten
|
13
|
+
* deleted is back, misteriously disappeared in version 0.2
|
6
14
|
|
7
15
|
Example
|
8
16
|
=======
|
9
17
|
class Post
|
18
|
+
# Replace normal behavior of object completely
|
10
19
|
delete_softly
|
11
20
|
end
|
12
21
|
|
13
22
|
class Comment
|
23
|
+
# Rely on calling active for this object when needed
|
14
24
|
delete_softly false
|
15
25
|
end
|
16
26
|
|
17
27
|
Now the following stuff works:
|
28
|
+
== The Post model ==
|
18
29
|
p1 = Post.create
|
19
30
|
p2 = Post.create
|
20
31
|
Post.count #=> 2
|
@@ -29,5 +40,6 @@ Now the following stuff works:
|
|
29
40
|
Comment.count #=> 2 (Since we added false)
|
30
41
|
Comment.active.count #=> 1
|
31
42
|
|
43
|
+
See the rdoc for better examples and documentation
|
32
44
|
|
33
45
|
Copyright (c) 2010 [Benjamin ter Kuile], released under the MIT license
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ Rake::TestTask.new(:test) do |t|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
desc 'Echoe'
|
18
|
-
Echoe.new('delete_softly', '0.0.
|
18
|
+
Echoe.new('delete_softly', '0.0.3') do |p|
|
19
19
|
p.description = "Add soft delete functionality to your ActiveRecord models"
|
20
20
|
p.url = "http://github.com/bterkuile/delete_softly"
|
21
21
|
p.author = "Benjamin ter Kuile"
|
data/delete_softly.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{delete_softly}
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.3"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Benjamin ter Kuile"]
|
9
|
-
s.date = %q{2010-
|
9
|
+
s.date = %q{2010-12-17}
|
10
10
|
s.description = %q{Add soft delete functionality to your ActiveRecord models}
|
11
11
|
s.email = %q{bterkuile@gmail.com}
|
12
12
|
s.extra_rdoc_files = ["README", "lib/class_methods.rb", "lib/delete_softly.rb", "lib/instance_methods.rb"]
|
data/lib/class_methods.rb
CHANGED
@@ -2,25 +2,25 @@ module DeleteSoftly
|
|
2
2
|
module ClassMethods
|
3
3
|
|
4
4
|
# Give the representation of items at a certain date/time.
|
5
|
-
# class Item
|
5
|
+
# class Item < ActiveRecord::Base
|
6
6
|
# delete_softly
|
7
7
|
# end
|
8
8
|
# Will result in:
|
9
9
|
# Item.at_time(DateTime.parse('2010-01-01')) #=> (SELECT "items".* FROM "items" WHERE (((("items"."deleted_at" > '2010-01-01 00:00:00') OR ("items"."deleted_at" IS NULL)) AND ("items"."created_at" < '2010-01-01 00:00:00')))
|
10
|
-
def
|
10
|
+
def at_time(date = Time.now.utc)
|
11
11
|
with_deleted do
|
12
12
|
where(({:deleted_at.gt => date} | {:deleted_at => nil}) & {:created_at.lt => date})
|
13
13
|
end
|
14
14
|
end
|
15
|
-
alias_method :
|
15
|
+
alias_method :at_date, :at_time
|
16
16
|
|
17
17
|
# Give the currently active items. When delete_soflty is added this is invoked by default
|
18
18
|
# But when false is added, items are shown by default
|
19
|
-
# class Item
|
19
|
+
# class Item < ActiveRecord::Base
|
20
20
|
# delete_softly false
|
21
21
|
# end
|
22
22
|
# Or similar:
|
23
|
-
# class Item
|
23
|
+
# class Item < ActiveRecord::Base
|
24
24
|
# delete_softly :default => false
|
25
25
|
# end
|
26
26
|
# You need to call active on the model where you want to hide deleted items
|
@@ -30,8 +30,14 @@ module DeleteSoftly
|
|
30
30
|
where(:deleted_at => nil)
|
31
31
|
end
|
32
32
|
|
33
|
+
# Same as active, but not to be overwritten. Active might become with disabled => false
|
34
|
+
# or something like that. Without deleted should remain intact
|
35
|
+
def without_deleted
|
36
|
+
where(:deleted_at => nil)
|
37
|
+
end
|
38
|
+
|
33
39
|
# Include deleted items when performing queries
|
34
|
-
# class Item
|
40
|
+
# class Item < ActiveRecord::Base
|
35
41
|
# default_scope order(:content)
|
36
42
|
# delete_softly
|
37
43
|
# end
|
@@ -42,7 +48,8 @@ module DeleteSoftly
|
|
42
48
|
# Item.with_deleted do
|
43
49
|
# Item.where(:content.matches => 'a%') #=> SELECT "items".* FROM "items" WHERE ("items"."content" ILIKE 'a%') ORDER BY "items"."content"
|
44
50
|
# end
|
45
|
-
# IHaveManyItems.items #=> SELECT "items".* FROM "items" WHERE ("items"."deleted_at" IS NULL) AND ("items".i_have_many_items_id = 1) ORDER BY "items"."content"
|
51
|
+
# IHaveManyItems.first.items #=> SELECT "items".* FROM "items" WHERE ("items"."deleted_at" IS NULL) AND ("items".i_have_many_items_id = 1) ORDER BY "items"."content"
|
52
|
+
# IHaveManyItems.first.items.with_deleted #=> SELECT "items".* FROM "items" WHERE ("items".i_have_many_items_id = 1) ORDER BY "items"."content"
|
46
53
|
def with_deleted(&block)
|
47
54
|
if scoped_methods.any? # There are scoped methods in place
|
48
55
|
|
@@ -63,7 +70,20 @@ module DeleteSoftly
|
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
66
|
-
|
73
|
+
def deleted
|
74
|
+
with_deleted.where(:deleted_at.ne => nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Support for paper_trail if it is installed as well. Then you can use:
|
78
|
+
# class Post < ActiveRecord::Base
|
79
|
+
# default_scope order(:created_at)
|
80
|
+
# delete_softly
|
81
|
+
# has_paper_trail
|
82
|
+
# has_many :comments
|
83
|
+
# end
|
84
|
+
# Then
|
85
|
+
# Post.version_at(1.week.ago)
|
86
|
+
# Will return all post of one week ago, with the appropriate field values at that time
|
67
87
|
def version_at(time)
|
68
88
|
if respond_to?(:at_time)
|
69
89
|
at_time(time).map{|i| i.respond_to?(:version_at) ? i.version_at(time) : i}.compact
|
data/lib/delete_softly.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
# DeleteSoftly
|
2
|
+
# This is a gem/plugin that adds soft delete functionality to ActiveRecord.
|
3
|
+
# Take a look at the
|
4
|
+
# DeleteSoftly::ArExtender
|
5
|
+
# DeleteSoftly::ClassMethods
|
6
|
+
# DeleteSoftly::InstanceMethods
|
7
|
+
# to get a feel of what is being done. This gem works through many relations
|
8
|
+
|
2
9
|
require 'active_record'
|
3
10
|
require 'meta_where'
|
4
11
|
require 'class_methods'
|
@@ -8,26 +15,53 @@ module DeleteSoftly
|
|
8
15
|
|
9
16
|
# Always have Model.active available. It is a good practice to use it
|
10
17
|
# when you want the active records. Even use it when no logic is in
|
11
|
-
# place yet.
|
18
|
+
# place yet. Now it is an alias for scoped, but can be overwritten
|
19
|
+
# for custom behaviour, for example:
|
20
|
+
# class Post < ActiveRecord::Base
|
21
|
+
# delete_softly
|
22
|
+
# has_many :comments
|
23
|
+
# def self.active
|
24
|
+
# super.where(:disabled.ne => true)
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# class Comment < ActiveRecord::Base
|
28
|
+
# belongs_to :post
|
29
|
+
# end
|
30
|
+
# will result in:
|
31
|
+
# Post.all #=> SELECT * FROM posts WHERE deleted_at IS NULL;
|
32
|
+
# Post.active #=> SELECT * FROM posts WHERE deleted_at IS NULL AND disabled != 't';
|
33
|
+
# Comment.all #=> SELECT * FROM comments;
|
34
|
+
# Comment.active #=> SELECT * FROM comments;
|
12
35
|
def active
|
13
36
|
scoped
|
14
37
|
end
|
15
38
|
|
16
39
|
# Make the model delete softly. A deleted_at:datetime column is required
|
17
|
-
# for this to work
|
18
|
-
|
40
|
+
# for this to work.
|
41
|
+
# The two most important differences are that it can be enforce on a model
|
42
|
+
# or be more free.
|
43
|
+
# class Post < ActiveRecord::Base
|
44
|
+
# delete_softly
|
45
|
+
# end
|
46
|
+
# will enforce soft delete on the post model. A deleted post will never appear,
|
47
|
+
# unless the explicit with_deleted is called. When the model is:
|
48
|
+
# class Post < ActiveRecord::Base
|
49
|
+
# delete_softly :enforce => false
|
50
|
+
# end
|
51
|
+
# An object will still be available after destroy is called,
|
52
|
+
def delete_softly(options = {:enforce => :active})
|
19
53
|
# Make destroy! the old destroy
|
20
54
|
alias_method :destroy!, :destroy
|
21
55
|
|
22
56
|
include DeleteSoftly::InstanceMethods
|
23
57
|
extend DeleteSoftly::ClassMethods
|
24
58
|
# Support single argument
|
25
|
-
# delete_softly :active
|
26
|
-
# delete_softly :
|
27
|
-
options = {:
|
28
|
-
if options[:
|
29
|
-
if options[:
|
30
|
-
default_scope send(options[:
|
59
|
+
# delete_softly :active # Same as :enforce => :active, default behaviour
|
60
|
+
# delete_softly :enforce=> :with_deleted # Same as without argument
|
61
|
+
options = {:enforce=> options } unless options.is_a?(Hash)
|
62
|
+
if options[:enforce]
|
63
|
+
if options[:enforce].is_a?(Symbol) && respond_to?(options[:enforce])
|
64
|
+
default_scope send(options[:enforce])
|
31
65
|
else
|
32
66
|
default_scope active
|
33
67
|
end
|
data/lib/instance_methods.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module DeleteSoftly
|
2
2
|
module InstanceMethods
|
3
|
-
|
3
|
+
|
4
|
+
# Custom destroy method for models using delete_softly
|
5
|
+
def destroy
|
4
6
|
if persisted?
|
5
7
|
with_transaction_returning_status do
|
6
8
|
_run_destroy_callbacks do
|
@@ -12,8 +14,21 @@ module DeleteSoftly
|
|
12
14
|
@destroyed = true
|
13
15
|
freeze
|
14
16
|
end
|
15
|
-
|
17
|
+
|
18
|
+
# Revive a destroyed item. For a model like:
|
19
|
+
# class Post < ActiveRecord::Base
|
20
|
+
# delete_softly
|
21
|
+
# end
|
22
|
+
# Then the following can be done:
|
23
|
+
# p = Post.find(1)
|
24
|
+
# p.destroy
|
25
|
+
# p = Post.find(1) # raise error
|
26
|
+
# p = Post.with_deleted.find(1) # Original object but with deleted_at attribute set
|
27
|
+
# p.revive #=> deleted_at => nil
|
28
|
+
# p = Post.find(1) # business as usual
|
29
|
+
# If papertrail is used for this model it will not store a copy
|
16
30
|
def revive
|
31
|
+
# Disable paper_trail when it is present and active
|
17
32
|
if self.class.respond_to?(:paper_trail_active) && self.class.paper_trail_active
|
18
33
|
self.class.paper_trail_off
|
19
34
|
update_attribute :deleted_at, nil
|
data/test/delete_softly_test.rb
CHANGED
@@ -2,7 +2,41 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class DeleteSoftlyTest < ActiveSupport::TestCase
|
4
4
|
# Replace this with your real tests.
|
5
|
+
setup do
|
6
|
+
puts "New setup"
|
7
|
+
Post.with_deleted.delete_all
|
8
|
+
Comment.with_deleted.delete_all
|
9
|
+
Tag.delete_all
|
10
|
+
@post1 = Post.create(:title => "post1")
|
11
|
+
@post1_id = @post1.id
|
12
|
+
@comment1_1 = @post1.comments.create(:email => "comment1_1", :body => "Comment 1 for post 1")
|
13
|
+
@comment2_1 = @post1.comments.create("email" => "comment1_2", :body => "Comment 2 for post 1")
|
14
|
+
|
15
|
+
@post2 = Post.create(:title => "post2")
|
16
|
+
@post2_id = @post2.id
|
17
|
+
end
|
5
18
|
test "the truth" do
|
6
19
|
assert true
|
7
20
|
end
|
21
|
+
test "two records available" do
|
22
|
+
assert_equal 2, Post.count
|
23
|
+
end
|
24
|
+
|
25
|
+
test "destroy count test" do
|
26
|
+
@post1.destroy
|
27
|
+
assert_equal 1, Post.count
|
28
|
+
assert_equal 2, Post.with_deleted.count
|
29
|
+
assert_nil Post.find_by_id(@post1_id)
|
30
|
+
@post1 = Post.with_deleted.find(@post1_id)
|
31
|
+
assert @post1.deleted_at
|
32
|
+
@post1.revive
|
33
|
+
assert 2, Post.count
|
34
|
+
end
|
35
|
+
|
36
|
+
test "deleted, without_deleted methods" do
|
37
|
+
assert_equal [@post1, @post2], Post.all.sort_by{|p| p.title}
|
38
|
+
@post1.destroy
|
39
|
+
assert_equal [@post2], Post.without_deleted.all
|
40
|
+
assert_equal [@post1], Post.deleted.all
|
41
|
+
end
|
8
42
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,3 +1,57 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
|
+
require 'arel'
|
3
4
|
require 'active_support'
|
5
|
+
require 'active_support/all'
|
6
|
+
require 'lib/delete_softly'
|
7
|
+
require 'sqlite3'
|
8
|
+
|
9
|
+
|
10
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
11
|
+
ActiveRecord::Base.establish_connection(
|
12
|
+
:adapter => 'sqlite3',
|
13
|
+
:database => 'test/db.sqlite3'
|
14
|
+
)
|
15
|
+
|
16
|
+
class Post < ActiveRecord::Base
|
17
|
+
delete_softly
|
18
|
+
has_many :comments
|
19
|
+
end
|
20
|
+
|
21
|
+
class Comment < ActiveRecord::Base
|
22
|
+
delete_softly false
|
23
|
+
belongs_to :post
|
24
|
+
has_many :tags
|
25
|
+
end
|
26
|
+
|
27
|
+
class Tag < ActiveRecord::Base
|
28
|
+
belongs_to :comment
|
29
|
+
end
|
30
|
+
|
31
|
+
unless Post.table_exists?
|
32
|
+
puts "Creating table posts"
|
33
|
+
ActiveRecord::Base.connection.create_table "posts" do |t|
|
34
|
+
t.string :title
|
35
|
+
t.text :body
|
36
|
+
t.datetime :deleted_at
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
end
|
40
|
+
unless Comment.table_exists?
|
41
|
+
puts "Creating table comments"
|
42
|
+
ActiveRecord::Base.connection.create_table "comments" do |t|
|
43
|
+
t.string :email
|
44
|
+
t.text :body
|
45
|
+
t.integer :post_id
|
46
|
+
t.datetime :deleted_at
|
47
|
+
t.timestamps
|
48
|
+
end
|
49
|
+
end
|
50
|
+
unless Tag.table_exists?
|
51
|
+
puts "Creating table tags"
|
52
|
+
ActiveRecord::Base.connection.create_table "tags" do |t|
|
53
|
+
t.string :name
|
54
|
+
t.integer :comment_id
|
55
|
+
t.timestamps
|
56
|
+
end
|
57
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delete_softly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Benjamin ter Kuile
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-17 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|