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 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: