kakurenbo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ script: bundle exec rspec
3
+ before_install:
4
+ - gem install bundler
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.1.0
8
+ env:
9
+ - RAILS=3.2.16
10
+ - RAILS=4.0.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kakurenbo.gemspec
4
+ gemspec
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,3 @@
1
+ module Kakurenbo
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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: