delete_softly 0.0.2 → 0.0.3
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/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
|