kakurenbo 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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/Rakefile +1 -0
- data/kakurenbo.gemspec +42 -0
- data/lib/kakurenbo.rb +11 -0
- data/lib/kakurenbo/mixin_ar_base.rb +51 -0
- data/lib/kakurenbo/soft_delete_core.rb +129 -0
- data/lib/kakurenbo/version.rb +3 -0
- data/spec/kakurenbo/mixin_ar_base_spec.rb +57 -0
- data/spec/kakurenbo/soft_delete_core_spec.rb +420 -0
- data/spec/spec_helper.rb +31 -0
- metadata +153 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 55d1fd72841d413af8d2a8337eb560bc52c5f30c
|
4
|
+
data.tar.gz: 8f41f354916861de37858bd4a9e6110100bd2cdb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d063beccd963344db1f4197da40b92991209232c39b04fb64dd448fe407f5e0cbf87ee9e8d259006cfaf5b4a78c9775a49cc23c38092a30664d29ef5fe5f78c0
|
7
|
+
data.tar.gz: 7c87e213b0e260bdcda9bf8c007b75230a13404912ac39f77f40b560b8d8523f299960a4488290707cb0834e578da30ade2e4da08392fa50e1833a374907b507
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
vendor/bundle
|
19
|
+
|
20
|
+
# --- IDE Project files ---
|
21
|
+
# AptanaStudio
|
22
|
+
.project
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Atsushi Nakamura
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Kakurenbo
|
2
|
+
|
3
|
+
Kakurenbo provides soft delete.
|
4
|
+
Kakurenbo is a re-implementation of [paranoia](http://github.com/radar/paranoia) and [acts\_as\_paranoid](http://github.com/technoweenie/acts_as_paranoid) for Rails4 and 3. implemented a function that other gems are not enough.
|
5
|
+
|
6
|
+
The usage of the Kakurenbo is very very very simple. Only add `deleted_at`(datetime) to column.
|
7
|
+
Of course you can use `acts_as_paranoid`.In addition, Kakurenbo has many advantageous.
|
8
|
+
|
9
|
+
|
10
|
+
# Installation
|
11
|
+
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'kakurenbo'
|
15
|
+
```
|
16
|
+
|
17
|
+
# Usage
|
18
|
+
You need only to add 'deleted_at' to model.
|
19
|
+
|
20
|
+
```shell
|
21
|
+
rails generate migration AddDeletedAtToModels deleted_at:datetime
|
22
|
+
```
|
23
|
+
The model having deleted_at becomes able to soft-delete automatically.
|
24
|
+
|
25
|
+
_Kakurenbo provides `acts_as_paranoid` method for compatibility._
|
26
|
+
|
27
|
+
|
28
|
+
## Basic Example
|
29
|
+
|
30
|
+
### soft-delete
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
model.destroy
|
34
|
+
```
|
35
|
+
|
36
|
+
### restore a record
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
model.restore!
|
40
|
+
|
41
|
+
# This is usable, too.
|
42
|
+
Model.restore(id)
|
43
|
+
```
|
44
|
+
|
45
|
+
When restore, call restore callbacks.`before_restore` `after_restore`
|
46
|
+
|
47
|
+
|
48
|
+
### hard-delete
|
49
|
+
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
model = Model.new
|
53
|
+
model.destroy!
|
54
|
+
```
|
55
|
+
|
56
|
+
### check if a record is fotdeleted
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
model.destroyed?
|
60
|
+
```
|
61
|
+
|
62
|
+
### find with soft-deleted
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
Model.with_deleted
|
66
|
+
```
|
67
|
+
|
68
|
+
|
69
|
+
### find only the soft-deleted
|
70
|
+
|
71
|
+
``` ruby
|
72
|
+
Model.only_deleted
|
73
|
+
```
|
74
|
+
|
75
|
+
|
76
|
+
# License
|
77
|
+
This gem is released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/kakurenbo.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kakurenbo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kakurenbo"
|
8
|
+
spec.version = Kakurenbo::VERSION
|
9
|
+
spec.authors = ["alfa-jpn"]
|
10
|
+
spec.email = ["a.nkmr.ja@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = <<-EOF
|
13
|
+
provides soft delete.
|
14
|
+
Kakurenbo is a re-implementation of paranoia and acts_as_paranoid for Rails4 and 3.
|
15
|
+
implemented a function that other gems are not enough.
|
16
|
+
EOF
|
17
|
+
|
18
|
+
spec.description = <<-EOF
|
19
|
+
provides soft delete.
|
20
|
+
Kakurenbo is a re-implementation of paranoia and acts_as_paranoid for Rails4 and 3.
|
21
|
+
implemented a function that other gems are not enough.
|
22
|
+
|
23
|
+
The usage of the Kakurenbo is very very very simple. Only add `deleted_at`(datetime) to column.
|
24
|
+
Of course you can use `acts_as_paranoid`.In addition, Kakurenbo has many advantageous.
|
25
|
+
EOF
|
26
|
+
|
27
|
+
spec.homepage = "https://github.com/alfa-jpn/kakurenbo"
|
28
|
+
spec.license = "MIT"
|
29
|
+
|
30
|
+
spec.files = `git ls-files`.split($/)
|
31
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
32
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
36
|
+
spec.add_development_dependency "rake"
|
37
|
+
spec.add_development_dependency "rspec"
|
38
|
+
spec.add_development_dependency "yard"
|
39
|
+
spec.add_development_dependency "sqlite3"
|
40
|
+
|
41
|
+
spec.add_dependency 'activerecord', '>= 3.2.0'
|
42
|
+
end
|
data/lib/kakurenbo.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# require need modules.
|
2
|
+
require "active_support"
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
# require kakurenbo modules.
|
6
|
+
require "kakurenbo/version"
|
7
|
+
require "kakurenbo/mixin_ar_base"
|
8
|
+
require "kakurenbo/soft_delete_core"
|
9
|
+
|
10
|
+
# Kakurenbo Mixin to ActiveRecord::Base.
|
11
|
+
ActiveRecord::Base.send :include, Kakurenbo::MixinARBase
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Kakurenbo
|
2
|
+
module MixinARBase
|
3
|
+
# Extend ClassMethods after include.
|
4
|
+
def self.included(base_class)
|
5
|
+
base_class.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Initialize Kakurenbo in child class.
|
10
|
+
def inherited(child_class)
|
11
|
+
child_class.instance_eval {
|
12
|
+
next unless column_names.include?('deleted_at')
|
13
|
+
remodel_as_soft_delete
|
14
|
+
}
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Remodel Model as soft-delete.
|
19
|
+
#
|
20
|
+
# @options [Hash] option
|
21
|
+
# default: {
|
22
|
+
# :column => :deleted_at
|
23
|
+
# }
|
24
|
+
def remodel_as_soft_delete(options = {})
|
25
|
+
options.reverse_merge!(
|
26
|
+
:column => :deleted_at
|
27
|
+
)
|
28
|
+
|
29
|
+
unless paranoid?
|
30
|
+
alias_method :delete!, :delete
|
31
|
+
alias_method :destroy!, :destroy
|
32
|
+
|
33
|
+
class_attribute :kakurenbo_column
|
34
|
+
self.kakurenbo_column = options[:column]
|
35
|
+
|
36
|
+
include Kakurenbo::SoftDeleteCore
|
37
|
+
end
|
38
|
+
end
|
39
|
+
alias_method :acts_as_paranoid, :remodel_as_soft_delete
|
40
|
+
|
41
|
+
# Will be override this method, if class is soft_delete.
|
42
|
+
def paranoid?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def paranoid?
|
48
|
+
self.class.paranoid?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Kakurenbo
|
2
|
+
module SoftDeleteCore
|
3
|
+
# Extend ClassMethods after include.
|
4
|
+
def self.included(base_class)
|
5
|
+
base_class.extend ClassMethods
|
6
|
+
base_class.extend Callbacks
|
7
|
+
base_class.extend Scopes
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def paranoid?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Restore models.
|
16
|
+
#
|
17
|
+
# @param id [Array or Integer] id or ids.
|
18
|
+
# @param options [Hash] options(same restore of instance methods.)
|
19
|
+
def restore(id, options = {})
|
20
|
+
only_deleted.where(:id => id).each{|m| m.restore!(options)}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Callbacks
|
25
|
+
def self.extended(base_class)
|
26
|
+
# Regist callbacks.
|
27
|
+
[:before, :around, :after].each do |on|
|
28
|
+
base_class.define_singleton_method("#{on}_restore") do |*args, &block|
|
29
|
+
set_callback(:restore, on, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
base_class.define_callbacks :restore
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module Scopes
|
37
|
+
def self.extended(base_class)
|
38
|
+
base_class.instance_eval {
|
39
|
+
scope :only_deleted, ->{ with_deleted.where.not( kakurenbo_column => nil ) }
|
40
|
+
scope :with_deleted, ->{ all.tap{ |s| s.default_scoped = false } }
|
41
|
+
scope :without_deleted, ->{ where(kakurenbo_column => nil) }
|
42
|
+
|
43
|
+
default_scope ->{ without_deleted }
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete
|
49
|
+
return if new_record? or destroyed?
|
50
|
+
update_column kakurenbo_column, current_time_from_proper_timezone
|
51
|
+
end
|
52
|
+
|
53
|
+
def destroy
|
54
|
+
return if destroyed?
|
55
|
+
with_transaction_returning_status {
|
56
|
+
destroy_at = current_time_from_proper_timezone
|
57
|
+
run_callbacks(:destroy){ update_column kakurenbo_column, destroy_at }
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroyed?
|
62
|
+
!send(kakurenbo_column).nil?
|
63
|
+
end
|
64
|
+
alias_method :deleted?, :destroyed?
|
65
|
+
|
66
|
+
def kakurenbo_column
|
67
|
+
self.class.kakurenbo_column
|
68
|
+
end
|
69
|
+
|
70
|
+
def persisted?
|
71
|
+
!new_record?
|
72
|
+
end
|
73
|
+
|
74
|
+
# restore record.
|
75
|
+
#
|
76
|
+
# @param options [Hash] options.
|
77
|
+
# defaults: {
|
78
|
+
# recursive: true
|
79
|
+
# }
|
80
|
+
def restore!(options = {})
|
81
|
+
options.reverse_merge!(
|
82
|
+
:recursive => true
|
83
|
+
)
|
84
|
+
|
85
|
+
with_transaction_returning_status {
|
86
|
+
run_callbacks(:restore) do
|
87
|
+
parent_deleted_at = send(kakurenbo_column)
|
88
|
+
update_column kakurenbo_column, nil
|
89
|
+
restore_associated_records(parent_deleted_at) if options[:recursive]
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
alias_method :restore, :restore!
|
94
|
+
|
95
|
+
private
|
96
|
+
# Calls the given block once for each dependent destroy records.
|
97
|
+
# @note Only call the class of paranoid.
|
98
|
+
#
|
99
|
+
# @param &block [Proc{|record|.. }] execute block.
|
100
|
+
def each_dependent_destroy_records(&block)
|
101
|
+
self.class.reflect_on_all_associations.each do |association|
|
102
|
+
next unless association.options[:dependent] == :destroy
|
103
|
+
next unless association.klass.paranoid?
|
104
|
+
|
105
|
+
resource = send(association.name)
|
106
|
+
next if resource.nil?
|
107
|
+
|
108
|
+
if association.collection?
|
109
|
+
resource = resource.only_deleted
|
110
|
+
else
|
111
|
+
resource = (resource.destroyed?) ? [resource] : []
|
112
|
+
end
|
113
|
+
|
114
|
+
resource.each &block
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Restore associated records.
|
119
|
+
# @note Not restore if deleted_at older than parent_deleted_at.
|
120
|
+
#
|
121
|
+
# @param parent_deleted_at [Time] The time when parent was deleted.
|
122
|
+
def restore_associated_records(parent_deleted_at)
|
123
|
+
each_dependent_destroy_records do |record|
|
124
|
+
next unless parent_deleted_at <= record.send(kakurenbo_column)
|
125
|
+
record.restore!
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kakurenbo::MixinARBase do
|
4
|
+
create_temp_table(:hard_deletes) {}
|
5
|
+
create_temp_table(:soft_deletes) {|t| t.datetime :deleted_at}
|
6
|
+
create_temp_table(:other_columns){|t| t.datetime :destroyed_at}
|
7
|
+
|
8
|
+
context 'when mixin ActiveRecord::Base' do
|
9
|
+
it 'has paranoid? in class methods.'do
|
10
|
+
expect(ActiveRecord::Base.methods).to include(:paranoid?)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has paranoid? in instance methods.' do
|
14
|
+
expect(ActiveRecord::Base.instance_methods).to include(:paranoid?)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'has acts_as_paranoid in class methods.' do
|
18
|
+
expect(ActiveRecord::Base.methods).to include(:acts_as_paranoid)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when define class of HardDelete' do
|
23
|
+
before :all do
|
24
|
+
class HardDelete < ActiveRecord::Base; end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'paranoid? return false.' do
|
28
|
+
expect(HardDelete.paranoid?).to be_false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when define class of SoftDelete' do
|
33
|
+
before :all do
|
34
|
+
class SoftDelete < ActiveRecord::Base; end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'paranoid? return true.' do
|
38
|
+
expect(SoftDelete.paranoid?).to be_true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when define class of OtherColumn' do
|
43
|
+
before :all do
|
44
|
+
class OtherColumn < ActiveRecord::Base
|
45
|
+
acts_as_paranoid :column => :destroyed_at
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'paranoid? return true.' do
|
50
|
+
expect(OtherColumn.paranoid?).to be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'kakurenbo_column is `:destroyed_at`.' do
|
54
|
+
expect(OtherColumn.kakurenbo_column).to eq(:destroyed_at)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,420 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kakurenbo::MixinARBase do
|
4
|
+
create_temp_table(:paranoid_models){ |t| t.datetime :deleted_at }
|
5
|
+
|
6
|
+
create_temp_table(:parent_models) { |t| t.integer :child_id; t.datetime :deleted_at }
|
7
|
+
create_temp_table(:normal_child_models) { |t| t.integer :parent_model_id }
|
8
|
+
create_temp_table(:paranoid_child_models) { |t| t.integer :parent_model_id; t.datetime :deleted_at }
|
9
|
+
create_temp_table(:paranoid_single_child_models) { |t| t.integer :parent_model_id; t.datetime :deleted_at }
|
10
|
+
|
11
|
+
context 'when define ParanoidModel' do
|
12
|
+
before :each do
|
13
|
+
class ParanoidModel < ActiveRecord::Base; end
|
14
|
+
end
|
15
|
+
|
16
|
+
after :each do
|
17
|
+
Object.class_eval{ remove_const :ParanoidModel }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'Callbacks' do
|
21
|
+
before :each do
|
22
|
+
@callback_model = ParanoidModel.create!
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'before_restore.' do
|
26
|
+
ParanoidModel.before_restore :before_restore_callback
|
27
|
+
@callback_model.should_receive(:before_restore_callback).once
|
28
|
+
@callback_model.run_callbacks(:restore)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'around_restore.' do
|
32
|
+
ParanoidModel.around_restore :around_restore_callback
|
33
|
+
@callback_model.should_receive(:around_restore_callback).once
|
34
|
+
@callback_model.run_callbacks(:restore)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'after_restore.' do
|
38
|
+
ParanoidModel.after_restore :after_restore_callback
|
39
|
+
@callback_model.should_receive(:after_restore_callback).once
|
40
|
+
@callback_model.run_callbacks(:restore)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
describe 'Scopes' do
|
46
|
+
before :each do
|
47
|
+
@normal_model = ParanoidModel.create!
|
48
|
+
@deleted_model = ParanoidModel.create!(deleted_at: Time.now)
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when use default_scope' do
|
52
|
+
it 'show normal model.' do
|
53
|
+
expect(ParanoidModel.find_by(id: @normal_model.id)).not_to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'hide deleted model.' do
|
57
|
+
expect(ParanoidModel.find_by(id: @deleted_model.id)).to be_nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when use only_deleted' do
|
62
|
+
it 'show normal model.' do
|
63
|
+
expect(ParanoidModel.only_deleted.find_by(id: @normal_model.id)).to be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'show deleted model.' do
|
67
|
+
expect(ParanoidModel.only_deleted.find_by(@deleted_model.id)).not_to be_nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when use with_deleted' do
|
72
|
+
it 'show normal model.' do
|
73
|
+
expect(ParanoidModel.with_deleted.find_by(id: @normal_model.id)).not_to be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'show deleted model.' do
|
77
|
+
expect(ParanoidModel.with_deleted.find_by(@deleted_model.id)).not_to be_nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when use without_deleted' do
|
82
|
+
it 'show normal model.' do
|
83
|
+
expect(ParanoidModel.without_deleted.find_by(id: @normal_model.id)).not_to be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'hide deleted model.' do
|
87
|
+
expect(ParanoidModel.without_deleted.find_by(id: @deleted_model.id)).to be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
describe 'Core' do
|
94
|
+
before :each do
|
95
|
+
@model = ParanoidModel.create!
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when delete' do
|
99
|
+
it 'soft-delete model.' do
|
100
|
+
expect{
|
101
|
+
@model.delete
|
102
|
+
}.to change(ParanoidModel, :count).by(-1)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'not hard-delete model.' do
|
106
|
+
expect{
|
107
|
+
@model.delete
|
108
|
+
}.to change{
|
109
|
+
ParanoidModel.all.with_deleted.count
|
110
|
+
}.by(0)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'call callbacks nothing.' do
|
114
|
+
ParanoidModel.before_destroy :before_destroy_callback
|
115
|
+
ParanoidModel.after_destroy :after_destroy_callback
|
116
|
+
ParanoidModel.after_commit :after_commit_callback
|
117
|
+
|
118
|
+
@model.should_receive(:before_destroy_callback).exactly(0).times
|
119
|
+
@model.should_receive(:after_destroy_callback).exactly(0).times
|
120
|
+
@model.should_receive(:after_commit_callback).exactly(0).times
|
121
|
+
|
122
|
+
@model.delete
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'not call callbacks other.' do
|
126
|
+
ParanoidModel.before_update :before_update_callback
|
127
|
+
ParanoidModel.before_save :before_save_callback
|
128
|
+
ParanoidModel.validate :validate_callback
|
129
|
+
|
130
|
+
@model.should_receive(:before_update_callback).exactly(0).times
|
131
|
+
@model.should_receive(:before_save_callback).exactly(0).times
|
132
|
+
@model.should_receive(:validate_callback).exactly(0).times
|
133
|
+
|
134
|
+
@model.delete
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when delete!' do
|
139
|
+
it 'hard-delete model.' do
|
140
|
+
expect{
|
141
|
+
@model.delete!
|
142
|
+
}.to change{
|
143
|
+
ParanoidModel.all.with_deleted.count
|
144
|
+
}.by(-1)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when destroy' do
|
149
|
+
it 'soft-delete model.' do
|
150
|
+
expect{
|
151
|
+
@model.destroy
|
152
|
+
}.to change(ParanoidModel, :count).by(-1)
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'not hard-delete model.' do
|
156
|
+
expect{
|
157
|
+
@model.destroy
|
158
|
+
}.to change{
|
159
|
+
ParanoidModel.all.with_deleted.count
|
160
|
+
}.by(0)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'call callbacks of destroy.' do
|
164
|
+
ParanoidModel.before_destroy :before_destroy_callback
|
165
|
+
ParanoidModel.after_destroy :after_destroy_callback
|
166
|
+
ParanoidModel.after_commit :after_commit_callback
|
167
|
+
|
168
|
+
@model.should_receive(:before_destroy_callback).once
|
169
|
+
@model.should_receive(:after_destroy_callback).once
|
170
|
+
@model.should_receive(:after_commit_callback).once.and_return(true)
|
171
|
+
|
172
|
+
@model.destroy
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'not call callbacks without destroy.' do
|
176
|
+
ParanoidModel.before_update :before_update_callback
|
177
|
+
ParanoidModel.before_save :before_save_callback
|
178
|
+
ParanoidModel.validate :validate_callback
|
179
|
+
|
180
|
+
@model.should_receive(:before_update_callback).exactly(0).times
|
181
|
+
@model.should_receive(:before_save_callback).exactly(0).times
|
182
|
+
@model.should_receive(:validate_callback).exactly(0).times
|
183
|
+
|
184
|
+
@model.destroy
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'when destroy!' do
|
189
|
+
it 'hard-delete model.' do
|
190
|
+
expect{
|
191
|
+
@model.destroy!
|
192
|
+
}.to change{
|
193
|
+
ParanoidModel.all.with_deleted.count
|
194
|
+
}.by(-1)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'when destroyed?' do
|
199
|
+
it 'false if model not destroyed.' do
|
200
|
+
expect(@model.destroyed?).to be_false
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'false if model destroyed.' do
|
204
|
+
@model.destroy
|
205
|
+
expect(@model.destroyed?).to be_true
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'alias_method deleted? to destroyed?' do
|
209
|
+
expect(@model.deleted?).to be_false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when restore.(class method)' do
|
214
|
+
before :each do
|
215
|
+
@model.destroy
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'with id restore of instance method.' do
|
219
|
+
expect{
|
220
|
+
ParanoidModel.restore(@model.id)
|
221
|
+
}.to change(ParanoidModel, :count).by(1)
|
222
|
+
|
223
|
+
expect(@model.reload.destroyed?).to be_false
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'with ids restore of instance method.' do
|
227
|
+
@model2 = ParanoidModel.create!
|
228
|
+
@model2.destroy
|
229
|
+
|
230
|
+
expect{
|
231
|
+
ParanoidModel.restore([@model.id, @model2.id])
|
232
|
+
}.to change(ParanoidModel, :count).by(2)
|
233
|
+
|
234
|
+
expect(@model.reload.destroyed?).to be_false
|
235
|
+
expect(@model2.reload.destroyed?).to be_false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'when restore.(instance method)' do
|
240
|
+
before :each do
|
241
|
+
@model.destroy
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'restore model' do
|
245
|
+
expect{
|
246
|
+
@model.restore!
|
247
|
+
}.to change(ParanoidModel, :count).by(1)
|
248
|
+
|
249
|
+
expect(@model.reload.destroyed?).to be_false
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'call callbacks of restore.' do
|
253
|
+
ParanoidModel.before_restore :before_restore_callback
|
254
|
+
ParanoidModel.after_restore :after_restore_callback
|
255
|
+
ParanoidModel.after_commit :after_commit_callback
|
256
|
+
|
257
|
+
@model.should_receive(:before_restore_callback).once
|
258
|
+
@model.should_receive(:after_restore_callback).once
|
259
|
+
@model.should_receive(:after_commit_callback).once.and_return(true)
|
260
|
+
|
261
|
+
@model.restore!
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'not call callbacks without restore.' do
|
265
|
+
ParanoidModel.before_update :before_update_callback
|
266
|
+
ParanoidModel.before_save :before_save_callback
|
267
|
+
ParanoidModel.validate :validate_callback
|
268
|
+
|
269
|
+
@model.should_receive(:before_update_callback).exactly(0).times
|
270
|
+
@model.should_receive(:before_save_callback).exactly(0).times
|
271
|
+
@model.should_receive(:validate_callback).exactly(0).times
|
272
|
+
|
273
|
+
@model.restore!
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
context 'when define relation model' do
|
281
|
+
before :all do
|
282
|
+
class NormalChildModel < ActiveRecord::Base; end
|
283
|
+
class ParanoidChildModel < ActiveRecord::Base; end
|
284
|
+
class ParanoidSingleChildModel < ActiveRecord::Base; end
|
285
|
+
class ParentModel < ActiveRecord::Base
|
286
|
+
has_many :normal_child_models, :dependent => :destroy
|
287
|
+
has_many :paranoid_child_models, :dependent => :destroy
|
288
|
+
|
289
|
+
has_one :paranoid_single_child_model, :dependent => :destroy
|
290
|
+
belongs_to :child, :class_name => :ParanoidSingleChildModel, :dependent => :destroy
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe 'NormalChildModel' do
|
295
|
+
before :each do
|
296
|
+
@parent = ParentModel.create!
|
297
|
+
@first = @parent.normal_child_models.create!
|
298
|
+
@second = @parent.normal_child_models.create!
|
299
|
+
end
|
300
|
+
|
301
|
+
context 'when parent was destroyed' do
|
302
|
+
it 'remove normal_child_models' do
|
303
|
+
expect{@parent.destroy}.to change(NormalChildModel, :count).by(-2)
|
304
|
+
expect(@first.destroyed?).to be_true
|
305
|
+
expect(@second.destroyed?).to be_true
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
describe 'ParanoidChildModel' do
|
311
|
+
before :each do
|
312
|
+
@parent = ParentModel.create!
|
313
|
+
@first = @parent.paranoid_child_models.create!
|
314
|
+
@second = @parent.paranoid_child_models.create!
|
315
|
+
@third = @parent.paranoid_child_models.create!
|
316
|
+
end
|
317
|
+
|
318
|
+
context 'when parent was destroyed' do
|
319
|
+
it 'remove normal_child_models' do
|
320
|
+
expect{@parent.destroy}.to change(ParanoidChildModel, :count).by(-3)
|
321
|
+
expect(@first.reload.destroyed?).to be_true
|
322
|
+
expect(@second.reload.destroyed?).to be_true
|
323
|
+
expect(@third.reload.destroyed?).to be_true
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'restore with normal_child_models' do
|
327
|
+
@parent.destroy
|
328
|
+
expect{@parent.restore!}.to change(ParanoidChildModel, :count).by(3)
|
329
|
+
expect(@first.reload.destroyed?).to be_false
|
330
|
+
expect(@second.reload.destroyed?).to be_false
|
331
|
+
expect(@third.reload.destroyed?).to be_false
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'not restore model that was deleted before parent was deleted.' do
|
335
|
+
# Delete before parent was deleted.
|
336
|
+
delete_at = 1.second.ago
|
337
|
+
Time.stub(:now).and_return(delete_at)
|
338
|
+
@first.destroy
|
339
|
+
|
340
|
+
# Delete after first_child was deleted.
|
341
|
+
Time.unstub(:now)
|
342
|
+
@parent.destroy
|
343
|
+
|
344
|
+
expect{@parent.restore!}.to change(ParanoidChildModel, :count).by(2)
|
345
|
+
expect(@first.reload.destroyed?).to be_true
|
346
|
+
expect(@second.reload.destroyed?).to be_false
|
347
|
+
expect(@third.reload.destroyed?).to be_false
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
describe 'ParanoidSingleChildModel' do
|
353
|
+
before :each do
|
354
|
+
@parent = ParentModel.create!
|
355
|
+
@first = ParanoidSingleChildModel.create!(parent_model_id: @parent.id)
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'when parent was destroyed' do
|
359
|
+
it 'remove normal_child_models' do
|
360
|
+
expect{@parent.destroy}.to change(ParanoidSingleChildModel, :count).by(-1)
|
361
|
+
expect(@first.reload.destroyed?).to be_true
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'restore with normal_child_models' do
|
365
|
+
@parent.destroy
|
366
|
+
expect{@parent.restore!}.to change(ParanoidSingleChildModel, :count).by(1)
|
367
|
+
expect(@first.reload.destroyed?).to be_false
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'not restore model that was deleted before parent was deleted.' do
|
371
|
+
# Delete before parent was deleted.
|
372
|
+
delete_at = 1.second.ago
|
373
|
+
Time.stub(:now).and_return(delete_at)
|
374
|
+
@first.destroy
|
375
|
+
|
376
|
+
# Delete after first_child was deleted.
|
377
|
+
Time.unstub(:now)
|
378
|
+
@parent.destroy
|
379
|
+
|
380
|
+
expect{@parent.restore!}.to change(ParanoidSingleChildModel, :count).by(0)
|
381
|
+
expect(@first.reload.destroyed?).to be_true
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'ParanoidSingleChildModel' do
|
387
|
+
before :each do
|
388
|
+
@first = ParanoidSingleChildModel.create!
|
389
|
+
@parent = ParentModel.create!(child_id: @first.id)
|
390
|
+
end
|
391
|
+
|
392
|
+
context 'when parent was destroyed' do
|
393
|
+
it 'remove normal_child_models' do
|
394
|
+
expect{@parent.destroy}.to change(ParanoidSingleChildModel, :count).by(-1)
|
395
|
+
expect(@first.reload.destroyed?).to be_true
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'restore with normal_child_models' do
|
399
|
+
@parent.destroy
|
400
|
+
expect{@parent.restore!}.to change(ParanoidSingleChildModel, :count).by(1)
|
401
|
+
expect(@first.reload.destroyed?).to be_false
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'not restore model that was deleted before parent was deleted.' do
|
405
|
+
# Delete before parent was deleted.
|
406
|
+
delete_at = 1.second.ago
|
407
|
+
Time.stub(:now).and_return(delete_at)
|
408
|
+
@first.destroy
|
409
|
+
|
410
|
+
# Delete after first_child was deleted.
|
411
|
+
Time.unstub(:now)
|
412
|
+
@parent.destroy
|
413
|
+
|
414
|
+
expect{@parent.restore!}.to change(ParanoidSingleChildModel, :count).by(0)
|
415
|
+
expect(@first.reload.destroyed?).to be_true
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "kakurenbo"
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.mock_framework = :rspec
|
7
|
+
config.before(:all) {
|
8
|
+
Dir.mkdir('tmp') unless Dir.exists?('tmp')
|
9
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'tmp/rspec.sqlite')
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create temp table for test.
|
14
|
+
#
|
15
|
+
# @param name [Symbol] Name of table.
|
16
|
+
# @param &block [Proc{|t|...}] Definition column block.
|
17
|
+
def create_temp_table(name, &block)
|
18
|
+
raise 'No block given!' unless block_given?
|
19
|
+
|
20
|
+
before :all do
|
21
|
+
migration = ActiveRecord::Migration.new
|
22
|
+
migration.verbose = false
|
23
|
+
migration.create_table name, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
after :all do
|
27
|
+
migration = ActiveRecord::Migration.new
|
28
|
+
migration.verbose = false
|
29
|
+
migration.drop_table name
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kakurenbo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- alfa-jpn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.2.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.2.0
|
97
|
+
description: |2
|
98
|
+
provides soft delete.
|
99
|
+
Kakurenbo is a re-implementation of paranoia and acts_as_paranoid for Rails4 and 3.
|
100
|
+
implemented a function that other gems are not enough.
|
101
|
+
|
102
|
+
The usage of the Kakurenbo is very very very simple. Only add `deleted_at`(datetime) to column.
|
103
|
+
Of course you can use `acts_as_paranoid`.In addition, Kakurenbo has many advantageous.
|
104
|
+
email:
|
105
|
+
- a.nkmr.ja@gmail.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".gitignore"
|
111
|
+
- ".travis.yml"
|
112
|
+
- Gemfile
|
113
|
+
- LICENSE.txt
|
114
|
+
- README.md
|
115
|
+
- Rakefile
|
116
|
+
- kakurenbo.gemspec
|
117
|
+
- lib/kakurenbo.rb
|
118
|
+
- lib/kakurenbo/mixin_ar_base.rb
|
119
|
+
- lib/kakurenbo/soft_delete_core.rb
|
120
|
+
- lib/kakurenbo/version.rb
|
121
|
+
- spec/kakurenbo/mixin_ar_base_spec.rb
|
122
|
+
- spec/kakurenbo/soft_delete_core_spec.rb
|
123
|
+
- spec/spec_helper.rb
|
124
|
+
homepage: https://github.com/alfa-jpn/kakurenbo
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.2.0
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: provides soft delete. Kakurenbo is a re-implementation of paranoia and acts_as_paranoid
|
148
|
+
for Rails4 and 3. implemented a function that other gems are not enough.
|
149
|
+
test_files:
|
150
|
+
- spec/kakurenbo/mixin_ar_base_spec.rb
|
151
|
+
- spec/kakurenbo/soft_delete_core_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
has_rdoc:
|