destroyed_at 0.2.0 → 0.3.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 +4 -4
- data/.travis.yml +8 -1
- data/CHANGELOG.md +8 -0
- data/README.md +36 -2
- data/destroyed_at.gemspec +3 -1
- data/lib/destroyed_at.rb +12 -0
- data/lib/destroyed_at/has_many_association.rb +7 -1
- data/lib/destroyed_at/mapper.rb +26 -0
- data/lib/destroyed_at/routes.rb +26 -0
- data/lib/destroyed_at/version.rb +1 -1
- data/test/destroyed_at_test.rb +149 -174
- data/test/mapper_test.rb +72 -0
- data/test/scope_test.rb +52 -0
- data/test/test_helper.rb +57 -60
- metadata +38 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb825af31fcc38174f287636b93d18b81e14304e
|
4
|
+
data.tar.gz: b3ff1f1fea7d4b1a7c6d08637f891cfdf069b122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d3261f0ff7ab631122308add947cdf6b6e3dccfcbb274453db7b8905b7888babbfda33b76bb412b5056ffbc4019d7b59d53f9423f930a8b14fa668279f55754
|
7
|
+
data.tar.gz: 7e3edf023b3691dd43033a61f3ee287b36e3f73ec3120f1f300dc3a0cb2681e16ee744e931a9aa9435a2d71c04ec5a2059dc762acba6948b86eb5f3715aec2cc
|
data/.travis.yml
CHANGED
@@ -1,2 +1,9 @@
|
|
1
1
|
rvm:
|
2
|
-
|
2
|
+
- 2.0.0
|
3
|
+
|
4
|
+
notifications:
|
5
|
+
hipchat:
|
6
|
+
rooms:
|
7
|
+
secure: HD93T2k0sXQYPk/SLwROvvbQmMDqOxxdgd3eKhocYmH/nKcU/+p3xIls1qstnUh+G4kVwj2ZhWkYg7lfg4rmDfO7RlKWA9zTX3Aaed4cSF/Od54S1Dqwl12KGSITs/nrY2W5zQ+JJ5chFKRYuiRpqb4cPQeafJojccHdaYjYShM=
|
8
|
+
template:
|
9
|
+
- '@all %{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}'
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
## 0.3.0
|
2
|
+
|
3
|
+
* Fix issue with has_many destroy against regular models - Brian
|
4
|
+
Cardarella
|
5
|
+
* Relation.destoyed removes the destoyed scope and adds a scope
|
6
|
+
for records that have been destoyed - Dan McClain
|
7
|
+
* Added /restore route for restorable resources - Brian Cardarella &
|
8
|
+
Romina Vargas
|
data/README.md
CHANGED
@@ -37,6 +37,15 @@ end
|
|
37
37
|
Each model's table that is expected to have this behavior **must** have
|
38
38
|
a `destroyed_at` column of type `DateTime`.
|
39
39
|
|
40
|
+
It is recommended that you add an index on the model's `destroyed_at` column,
|
41
|
+
so that your database does not have to do a table scan for every query.
|
42
|
+
Only Postgres' query indexes will be of any benefit:
|
43
|
+
|
44
|
+
```db
|
45
|
+
CREATE INDEX ON users WHERE destroyed_at IS NULL;
|
46
|
+
CREATE INDEX ON users WHERE destroyed_at IS NOT NULL;
|
47
|
+
```
|
48
|
+
|
40
49
|
## Usage ##
|
41
50
|
Allows you to "destroy" an object without deleting the record or
|
42
51
|
associated records.
|
@@ -61,6 +70,16 @@ user.destroyed_at
|
|
61
70
|
# => <DateTime>
|
62
71
|
```
|
63
72
|
|
73
|
+
#### Warning: Dependent relations with destroy ####
|
74
|
+
Be careful when destroying parent relationships that have `dependent:
|
75
|
+
:destroy`. If the child
|
76
|
+
relationship does not also `include DestroyedAt`, then when you call
|
77
|
+
`#destroy` on the parent instance, you will delete the child record,
|
78
|
+
rather than simply marking it `destroyed_at`.
|
79
|
+
|
80
|
+
The same goes for destroying through relations that omit `include
|
81
|
+
DestroyedAt`, even if the parent and child models include the mixin.
|
82
|
+
|
64
83
|
### Restoring ####
|
65
84
|
When you'd like to "restore" a record, call the `#restore` method on
|
66
85
|
the instance. This will set its `#destroyed_at` value to `nil`, thereby
|
@@ -83,15 +102,25 @@ user.destroyed_at
|
|
83
102
|
# => nil
|
84
103
|
```
|
85
104
|
|
105
|
+
### Destroyed Scope ###
|
106
|
+
When you include the `DestroyedAt` module, it sets a default scope of
|
107
|
+
`where(destroyed_at: nil)`. It also defines a scope called `.destroyed`
|
108
|
+
which removes the default scope and finds the records of a relation
|
109
|
+
`where.not(destroyed_at: nil)`. This is different than calling `unscoped`
|
110
|
+
on the class/relation. Where unscoped will remove all scoping from a
|
111
|
+
relation, `.destroyed` only removes the default scope generated by the
|
112
|
+
`DestroyedAt` module, making it safe to call on relations.
|
113
|
+
|
86
114
|
### Callbacks ###
|
87
|
-
`before_restore` and `
|
88
|
-
model. They work similarly to the `before_destroy` and `
|
115
|
+
`before_restore`, `after_restore` and `around_restore` callbacks are added to your
|
116
|
+
model. They work similarly to the `before_destroy`, `after_destroy` and `around_restore`
|
89
117
|
callbacks.
|
90
118
|
|
91
119
|
```ruby
|
92
120
|
class User < ActiveRecord::Base
|
93
121
|
before_restore :before_restore_action
|
94
122
|
after_restore :after_restore_action
|
123
|
+
around_restore :around_restore_action
|
95
124
|
|
96
125
|
private
|
97
126
|
|
@@ -102,6 +131,11 @@ class User < ActiveRecord::Base
|
|
102
131
|
def after_restore_action
|
103
132
|
...
|
104
133
|
end
|
134
|
+
|
135
|
+
def around_restore_action
|
136
|
+
# before around
|
137
|
+
yield # restoring...
|
138
|
+
# after around
|
105
139
|
end
|
106
140
|
```
|
107
141
|
|
data/destroyed_at.gemspec
CHANGED
@@ -19,9 +19,11 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
20
20
|
spec.required_ruby_version = '>= 2.0'
|
21
21
|
|
22
|
+
spec.add_runtime_dependency "activerecord", "~> 4.0.0"
|
23
|
+
spec.add_runtime_dependency 'actionpack', '~> 4.0.0'
|
24
|
+
|
22
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
23
26
|
spec.add_development_dependency "rake"
|
24
|
-
spec.add_development_dependency "activerecord", "~> 4.0.0"
|
25
27
|
spec.add_development_dependency "minitest"
|
26
28
|
spec.add_development_dependency "m"
|
27
29
|
spec.add_development_dependency "sqlite3"
|
data/lib/destroyed_at.rb
CHANGED
@@ -2,6 +2,7 @@ require 'destroyed_at/version'
|
|
2
2
|
require 'destroyed_at/belongs_to_association'
|
3
3
|
require 'destroyed_at/has_many_association'
|
4
4
|
require 'destroyed_at/has_one_association'
|
5
|
+
require 'destroyed_at/mapper'
|
5
6
|
|
6
7
|
module DestroyedAt
|
7
8
|
def self.included(klass)
|
@@ -9,6 +10,17 @@ module DestroyedAt
|
|
9
10
|
default_scope { where(destroyed_at: nil) }
|
10
11
|
after_initialize :_set_destruction_state
|
11
12
|
define_model_callbacks :restore
|
13
|
+
extend ClassMethods
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def destroyed
|
19
|
+
query = all.with_default_scope
|
20
|
+
query.where_values.reject! do |node|
|
21
|
+
Arel::Nodes::Equality === node && node.left.name == 'destroyed_at' && node.right.nil?
|
22
|
+
end
|
23
|
+
query.where.not(destroyed_at: nil)
|
12
24
|
end
|
13
25
|
end
|
14
26
|
|
@@ -2,7 +2,13 @@ module DestroyedAt
|
|
2
2
|
module HasManyAssociation
|
3
3
|
def delete_records(records, method)
|
4
4
|
if method == :destroy
|
5
|
-
records.each
|
5
|
+
records.each do |r|
|
6
|
+
if r.respond_to?(:destroyed_at)
|
7
|
+
r.destroy(owner.destroyed_at)
|
8
|
+
else
|
9
|
+
r.destroy
|
10
|
+
end
|
11
|
+
end
|
6
12
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
7
13
|
else
|
8
14
|
super
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
|
3
|
+
module DestroyedAt::Routes
|
4
|
+
def set_member_mappings_for_resource
|
5
|
+
member do
|
6
|
+
put :restore if parent_resource.actions.include?(:restore)
|
7
|
+
end
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ActionDispatch::Routing::Mapper.send(:prepend, DestroyedAt::Routes)
|
13
|
+
|
14
|
+
module DestroyedAt::Resource
|
15
|
+
def default_actions
|
16
|
+
actions = super
|
17
|
+
if self.name.camelcase.singularize.constantize.included_modules.include?(DestroyedAt)
|
18
|
+
actions << :restore
|
19
|
+
end
|
20
|
+
|
21
|
+
actions
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
ActionDispatch::Routing::Mapper::Resources::SingletonResource.send(:prepend, DestroyedAt::Resource)
|
26
|
+
ActionDispatch::Routing::Mapper::Resource.send(:prepend, DestroyedAt::Resource)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
|
3
|
+
module DestroyedAt::Mapper
|
4
|
+
module Routes
|
5
|
+
def set_member_mappings_for_resource
|
6
|
+
member do
|
7
|
+
put :restore if parent_resource.actions.include?(:restore)
|
8
|
+
end
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Resource
|
14
|
+
def default_actions
|
15
|
+
actions = super
|
16
|
+
if self.name.camelcase.singularize.constantize.included_modules.include?(DestroyedAt)
|
17
|
+
actions << :restore
|
18
|
+
end
|
19
|
+
|
20
|
+
actions
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
ActionDispatch::Routing::Mapper.send(:prepend, DestroyedAt::Mapper::Routes)
|
26
|
+
ActionDispatch::Routing::Mapper::Resource.send(:prepend, DestroyedAt::Mapper::Resource)
|
data/lib/destroyed_at/version.rb
CHANGED
data/test/destroyed_at_test.rb
CHANGED
@@ -1,200 +1,175 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
describe '
|
4
|
-
|
5
|
-
user = User.create
|
6
|
-
user.destroy
|
7
|
-
User.all.must_be_empty
|
8
|
-
User.unscoped.load.wont_be_empty
|
9
|
-
end
|
3
|
+
describe 'destroying an activerecord instance' do
|
4
|
+
let(:post) { Post.create }
|
10
5
|
|
11
|
-
it '
|
6
|
+
it 'sets the timestamp it was destroyed at' do
|
12
7
|
time = Time.now
|
13
8
|
Timecop.freeze(time) do
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
post = Post.create
|
10
|
+
post.destroy
|
11
|
+
post.destroyed_at.must_equal time
|
17
12
|
end
|
18
13
|
end
|
19
14
|
|
20
|
-
it '
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
user.destroyed_at.must_be_nil
|
26
|
-
User.all.wont_be_empty
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'will run destroy callbacks' do
|
30
|
-
person = Person.create
|
31
|
-
person.before_flag.wont_equal true
|
32
|
-
person.after_flag.wont_equal true
|
33
|
-
person.destroy
|
34
|
-
person.before_flag.must_equal true
|
35
|
-
person.after_flag.must_equal true
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'will run restore callbacks' do
|
39
|
-
person = Person.create(:destroyed_at => DateTime.current)
|
40
|
-
person.before_flag.wont_equal true
|
41
|
-
person.after_flag.wont_equal true
|
42
|
-
person.restore
|
43
|
-
person.before_flag.must_equal true
|
44
|
-
person.after_flag.must_equal true
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'will properly destroy relations' do
|
48
|
-
user = User.create(:profile => Profile.new, :car => Car.new)
|
49
|
-
user.reload
|
50
|
-
user.profile.wont_be_nil
|
51
|
-
user.car.wont_be_nil
|
52
|
-
user.destroy
|
53
|
-
Profile.count.must_equal 0
|
54
|
-
Profile.unscoped.count.must_equal 1
|
55
|
-
Car.count.must_equal 0
|
56
|
-
Car.unscoped.count.must_equal 0
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'can restore relationships' do
|
60
|
-
timestamp = DateTime.current
|
61
|
-
user = User.create(:destroyed_at => timestamp)
|
62
|
-
Profile.create(:destroyed_at => timestamp, :user => user)
|
63
|
-
Profile.count.must_equal 0
|
64
|
-
user.reload
|
65
|
-
user.restore
|
66
|
-
Profile.count.must_equal 1
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'will not restore relationships that have no destroy dependency' do
|
70
|
-
user = User.create(:destroyed_at => DateTime.current, :show => Show.new(:destroyed_at => DateTime.current))
|
71
|
-
Show.count.must_equal 0
|
72
|
-
user.restore
|
73
|
-
Show.count.must_equal 0
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'will respect has many associations' do
|
77
|
-
timestamp = DateTime.current
|
78
|
-
user = User.create(:destroyed_at => timestamp)
|
79
|
-
Dinner.create(:destroyed_at => timestamp, :user => user)
|
80
|
-
Dinner.count.must_equal 0
|
81
|
-
user.reload
|
82
|
-
user.restore
|
83
|
-
Dinner.count.must_equal 1
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'will respect has many through associations' do
|
87
|
-
timestamp = DateTime.current
|
88
|
-
user = User.create(:destroyed_at => timestamp)
|
89
|
-
car = Car.create
|
90
|
-
fleet = Fleet.create(:destroyed_at => timestamp, :car => car)
|
91
|
-
user.fleets = [fleet]
|
92
|
-
user.cars.count.must_equal 0
|
93
|
-
user.reload
|
94
|
-
user.restore
|
95
|
-
user.cars.count.must_equal 1
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'properly sets #destroyed?' do
|
99
|
-
user = User.create
|
100
|
-
user.destroy
|
101
|
-
user.destroyed?.must_equal true
|
102
|
-
user = User.unscoped.last
|
103
|
-
user.destroyed?.must_equal true
|
104
|
-
user.restore
|
105
|
-
user.destroyed?.must_equal false
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'properly selects columns' do
|
109
|
-
User.create
|
110
|
-
User.select(:id).must_be_kind_of ActiveRecord::Relation
|
111
|
-
end
|
112
|
-
|
113
|
-
it 'only destroys and restores related dependents' do
|
114
|
-
2.times do
|
115
|
-
User.create(dinners: [Dinner.create, Dinner.create, Dinner.create])
|
116
|
-
end
|
15
|
+
it 'does not delete the record' do
|
16
|
+
post.destroy
|
17
|
+
Post.all.must_be_empty
|
18
|
+
Post.unscoped.load.wont_be_empty
|
19
|
+
end
|
117
20
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
21
|
+
it 'sets #destroyed?' do
|
22
|
+
post.destroy
|
23
|
+
post.destroyed?.must_equal true
|
24
|
+
post = Post.unscoped.last
|
25
|
+
post.destroyed?.must_equal true
|
26
|
+
post.restore
|
27
|
+
post.destroyed?.must_equal false
|
124
28
|
end
|
125
29
|
|
126
|
-
it '
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
user.restore
|
131
|
-
user.before_update_count.must_equal nil
|
30
|
+
it 'runs destroy callbacks' do
|
31
|
+
post.destroy_callback_count.must_equal nil
|
32
|
+
post.destroy
|
33
|
+
post.destroy_callback_count.must_equal 1
|
132
34
|
end
|
133
35
|
|
134
|
-
it '
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
user.restore
|
140
|
-
user.validation_count.must_equal 1
|
36
|
+
it 'does not run update callbacks' do
|
37
|
+
post.destroy
|
38
|
+
post.update_callback_count.must_equal nil
|
39
|
+
post.restore
|
40
|
+
post.update_callback_count.must_equal nil
|
141
41
|
end
|
142
42
|
|
143
43
|
it 'stays persisted after destruction' do
|
144
|
-
|
145
|
-
|
146
|
-
user.persisted?.must_equal true
|
44
|
+
post.destroy
|
45
|
+
post.persisted?.must_equal true
|
147
46
|
end
|
148
47
|
|
149
|
-
it '
|
150
|
-
|
151
|
-
|
48
|
+
it 'destroys dependent relation with DestroyedAt' do
|
49
|
+
post.comments.create
|
50
|
+
Post.count.must_equal 1
|
51
|
+
Comment.count.must_equal 1
|
52
|
+
post.destroy
|
53
|
+
Post.count.must_equal 0
|
54
|
+
Comment.count.must_equal 0
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'destroys dependent through relation with DestroyedAt' do
|
58
|
+
commenter = Commenter.create
|
59
|
+
Comment.create(:post => post, :commenter => commenter)
|
60
|
+
|
61
|
+
Commenter.count.must_equal 1
|
62
|
+
Comment.count.must_equal 1
|
63
|
+
post.destroy
|
64
|
+
Commenter.count.must_equal 1
|
65
|
+
Comment.count.must_equal 0
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'deletes dependent relations without DestroyedAt' do
|
69
|
+
category = Category.create
|
70
|
+
Categorization.create(:category => category, :post => post)
|
71
|
+
post.categorizations.count.must_equal 1
|
72
|
+
post.destroy
|
73
|
+
Categorization.unscoped.count.must_equal 0
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'restoring an activerecord instance' do
|
78
|
+
let(:author) { Author.create }
|
79
|
+
let(:timestamp) { DateTime.current }
|
80
|
+
let(:post) { Post.create(:destroyed_at => timestamp) }
|
81
|
+
|
82
|
+
it 'restores the record' do
|
83
|
+
Post.all.must_be_empty
|
84
|
+
post.reload
|
85
|
+
post.restore
|
86
|
+
post.destroyed_at.must_be_nil
|
87
|
+
Post.all.wont_be_empty
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'runs the restore callbacks' do
|
91
|
+
post.restore_callback_count.must_equal nil
|
92
|
+
post.restore
|
93
|
+
post.restore_callback_count.must_equal 1
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'does not run restore validations' do
|
97
|
+
initial_count = post.validation_count
|
98
|
+
post.restore
|
99
|
+
initial_count.must_equal post.validation_count
|
152
100
|
end
|
153
101
|
|
102
|
+
it 'restores a dependent has_many relation with DestroyedAt' do
|
103
|
+
Comment.create(:destroyed_at => timestamp, :post => post)
|
104
|
+
Comment.count.must_equal 0
|
105
|
+
post.reload
|
106
|
+
post.restore
|
107
|
+
Comment.count.must_equal 1
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'does not restore a non-dependent relation with DestroyedAt' do
|
111
|
+
Post.count.must_equal 0
|
112
|
+
Author.count.must_equal 0
|
113
|
+
post.reload
|
114
|
+
post.restore
|
115
|
+
Post.count.must_equal 1
|
116
|
+
Author.count.must_equal 0
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'restores a dependent through relation with DestroyedAt' do
|
120
|
+
commenter = Commenter.create
|
121
|
+
Comment.create(:post => post, :commenter => commenter, :destroyed_at => timestamp)
|
122
|
+
|
123
|
+
Commenter.count.must_equal 1
|
124
|
+
Comment.count.must_equal 0
|
125
|
+
post.reload
|
126
|
+
post.restore
|
127
|
+
Commenter.count.must_equal 1
|
128
|
+
Comment.count.must_equal 1
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'restores only the dependent relationships destroyed when the parent was destroyed' do
|
132
|
+
post = Post.create
|
133
|
+
comment_1 = Comment.create(post: post, destroyed_at: Time.now - 1.day)
|
134
|
+
comment_2 = Comment.create(post: post)
|
135
|
+
post.destroy
|
136
|
+
post.reload # We have to reload the object before restoring in the test
|
137
|
+
# because the in memory object has greater precision than
|
138
|
+
# the database records
|
139
|
+
post.restore
|
140
|
+
post.comments.wont_include comment_1
|
141
|
+
post.comments.must_include comment_2
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'deleting a record' do
|
154
146
|
it 'is not persisted after deletion' do
|
155
|
-
|
156
|
-
|
157
|
-
|
147
|
+
post = Post.create
|
148
|
+
post.delete
|
149
|
+
post.persisted?.must_equal false
|
158
150
|
end
|
159
151
|
|
160
152
|
it 'can delete destroyed records and they are marked as not persisted' do
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
user.destroy
|
183
|
-
user.restore
|
184
|
-
user.profile.must_equal profile_2
|
185
|
-
profile_1.reload
|
186
|
-
profile_1.destroyed_at.wont_equal nil
|
187
|
-
end
|
188
|
-
|
189
|
-
it 'destroys and restores dependencies in a belongs_to relationship' do
|
190
|
-
user = User.create
|
191
|
-
show = Show.create(user: user)
|
192
|
-
show.destroy
|
193
|
-
show.reload
|
194
|
-
user.reload
|
195
|
-
user.destroyed_at.wont_equal nil
|
196
|
-
show.restore
|
197
|
-
user.reload
|
198
|
-
user.destroyed_at.must_equal nil
|
153
|
+
post = Post.create
|
154
|
+
post.destroy
|
155
|
+
post.persisted?.must_equal true
|
156
|
+
post.delete
|
157
|
+
post.persisted?.must_equal false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'destroying an activerecord instance without DestroyedAt' do
|
162
|
+
it 'does not impact ActiveRecord::Relation.destroy' do
|
163
|
+
post = Post.create
|
164
|
+
categorization = Categorization.create(post: post)
|
165
|
+
post.categorizations.destroy(categorization.id)
|
166
|
+
post.categorizations.must_be_empty
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe 'creating a destroyed record' do
|
171
|
+
it 'does not allow new records with destroyed_at columns present to be marked persisted' do
|
172
|
+
post = Post.new(destroyed_at: Time.now)
|
173
|
+
post.persisted?.must_equal false
|
199
174
|
end
|
200
175
|
end
|
data/test/mapper_test.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_dispatch'
|
3
|
+
require 'action_dispatch/routing/route_set'
|
4
|
+
require 'action_controller'
|
5
|
+
|
6
|
+
class CommentsController < ActionController::Base; end
|
7
|
+
|
8
|
+
class MapperTest < ActiveSupport::TestCase
|
9
|
+
setup do
|
10
|
+
@set = ActionDispatch::Routing::RouteSet.new
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'adds restore route for DestroyedAt model plural resource' do
|
14
|
+
draw do
|
15
|
+
resources :comments
|
16
|
+
end
|
17
|
+
|
18
|
+
params = @set.recognize_path('/comments/:id/restore', method: 'put')
|
19
|
+
assert_equal params[:controller], 'comments'
|
20
|
+
assert_equal params[:action], 'restore'
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'adds restore route for DestroyedAt model singular resource' do
|
24
|
+
draw do
|
25
|
+
resource :comment
|
26
|
+
end
|
27
|
+
|
28
|
+
params = @set.recognize_path('/comment/restore', method: 'put')
|
29
|
+
assert_equal params[:controller], 'comments'
|
30
|
+
assert_equal params[:action], 'restore'
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'does not add restore route for non DestroyedAt plural resource' do
|
34
|
+
draw do
|
35
|
+
resources :authors
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
params = @set.recognize_path('/authors/:id/restore', method: 'put')
|
40
|
+
assert false, 'this should not be reached'
|
41
|
+
rescue ActionController::RoutingError
|
42
|
+
assert true, 'path not recognized'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
test 'does not add restore route for non DestroyedAt singular resource' do
|
47
|
+
draw do
|
48
|
+
resource :author
|
49
|
+
end
|
50
|
+
|
51
|
+
begin
|
52
|
+
params = @set.recognize_path('/author/restore', method: 'put')
|
53
|
+
assert false, 'this should not be reached'
|
54
|
+
rescue ActionController::RoutingError
|
55
|
+
assert true, 'path not recognized'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def draw(&block)
|
62
|
+
@set.draw(&block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def url_helpers
|
66
|
+
@set.url_helpers
|
67
|
+
end
|
68
|
+
|
69
|
+
def clear!
|
70
|
+
@set.clear!
|
71
|
+
end
|
72
|
+
end
|
data/test/scope_test.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe 'Scopes' do
|
4
|
+
let(:post) { Post.create! }
|
5
|
+
let(:comment_1) { post.comments.create! }
|
6
|
+
let(:comment_2) { post.comments.create! }
|
7
|
+
let(:comment_3) { Comment.create! }
|
8
|
+
let(:comment_4) { Comment.create! }
|
9
|
+
|
10
|
+
before do
|
11
|
+
comment_1
|
12
|
+
comment_2.destroy
|
13
|
+
comment_3
|
14
|
+
comment_4.destroy
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.destroyed' do
|
18
|
+
context 'Called on model' do
|
19
|
+
let(:destroyed_comments) { Comment.destroyed }
|
20
|
+
|
21
|
+
it 'returns records that have been destroyed' do
|
22
|
+
destroyed_comments.must_include comment_2
|
23
|
+
destroyed_comments.must_include comment_4
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not return current records' do
|
27
|
+
destroyed_comments.wont_include comment_1
|
28
|
+
destroyed_comments.wont_include comment_3
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'Called on relation' do
|
33
|
+
let(:destroyed_comments) { post.comments.destroyed }
|
34
|
+
|
35
|
+
it 'returns destroyed records beloning in the relation' do
|
36
|
+
destroyed_comments.must_include comment_2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not return destroyed records that are outside the relation' do
|
40
|
+
destroyed_comments.wont_include comment_4
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not return current records in the relation' do
|
44
|
+
destroyed_comments.wont_include comment_1
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not return current records that are outside the relation' do
|
48
|
+
destroyed_comments.wont_include comment_3
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -27,79 +27,76 @@ ActiveRecord::Base.establish_connection(
|
|
27
27
|
|
28
28
|
DatabaseCleaner.strategy = :truncation
|
29
29
|
|
30
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
31
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
32
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
33
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
34
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
35
|
-
ActiveRecord::Base.connection.execute(%{CREATE TABLE
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
has_many :dinners, :dependent => :destroy
|
42
|
-
has_one :show
|
43
|
-
has_many :fleets
|
44
|
-
has_many :cars, :through => :fleets, :dependent => :destroy
|
45
|
-
before_update :increment_callback_counter
|
46
|
-
validate :increment_validation_counter
|
30
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE authors (id INTEGER PRIMARY KEY);})
|
31
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE categories (id INTEGER PRIMARY KEY);})
|
32
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE categorizations (id INTEGER PRIMARY KEY, category_id INTEGER, post_id INTEGER);})
|
33
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE comments (id INTEGER PRIMARY KEY, commenter_id INTEGER, post_id INTEGER, destroyed_at DATETIME);})
|
34
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE commenters (id INTEGER PRIMARY KEY, destroyed_at DATETIME);})
|
35
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE images (id INTEGER PRIMARY KEY, post_id INTEGER);})
|
36
|
+
ActiveRecord::Base.connection.execute(%{CREATE TABLE posts (id INTEGER PRIMARY KEY, author_id INTEGER, destroyed_at DATETIME);})
|
37
|
+
|
38
|
+
class Author < ActiveRecord::Base
|
39
|
+
has_many :posts
|
40
|
+
end
|
47
41
|
|
48
|
-
|
42
|
+
class Category < ActiveRecord::Base
|
43
|
+
has_many :categorizations
|
44
|
+
has_many :posts, through: :categorizations
|
45
|
+
end
|
49
46
|
|
50
|
-
|
47
|
+
class Categorization < ActiveRecord::Base
|
48
|
+
belongs_to :category
|
49
|
+
belongs_to :post
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
class Comment < ActiveRecord::Base
|
53
|
+
include DestroyedAt
|
54
|
+
belongs_to :post
|
55
|
+
belongs_to :commenter
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
class Commenter < ActiveRecord::Base
|
59
|
+
include DestroyedAt
|
60
|
+
has_many :comments, dependent: :destroy
|
61
|
+
has_many :posts, through: :comments
|
61
62
|
end
|
62
63
|
|
63
|
-
class
|
64
|
-
|
65
|
-
after_destroy :set_after_flag
|
64
|
+
class Post < ActiveRecord::Base
|
65
|
+
include DestroyedAt
|
66
66
|
|
67
|
-
|
68
|
-
|
67
|
+
belongs_to :author
|
68
|
+
has_many :categories, through: :categorizations
|
69
|
+
has_many :categorizations, dependent: :destroy
|
70
|
+
has_many :comments, dependent: :destroy
|
71
|
+
has_many :commenters, through: :comments
|
69
72
|
|
70
|
-
|
73
|
+
before_destroy :increment_destroy_callback_counter
|
74
|
+
before_restore :increment_restore_callback_counter
|
75
|
+
before_update :increment_update_callback_counter
|
71
76
|
|
72
|
-
|
73
|
-
self.before_flag = true
|
74
|
-
end
|
77
|
+
validate :increment_validation_counter
|
75
78
|
|
76
|
-
|
77
|
-
self.after_flag = true
|
78
|
-
end
|
79
|
-
end
|
79
|
+
attr_accessor :destroy_callback_count, :restore_callback_count, :update_callback_count, :validation_count
|
80
80
|
|
81
|
-
|
82
|
-
include DestroyedAt
|
83
|
-
belongs_to :user
|
84
|
-
end
|
81
|
+
private
|
85
82
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
83
|
+
def increment_restore_callback_counter
|
84
|
+
self.restore_callback_count ||= 0
|
85
|
+
self.restore_callback_count = self.restore_callback_count + 1
|
86
|
+
end
|
90
87
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
88
|
+
def increment_destroy_callback_counter
|
89
|
+
self.destroy_callback_count ||= 0
|
90
|
+
self.destroy_callback_count = self.destroy_callback_count + 1
|
91
|
+
end
|
95
92
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
93
|
+
def increment_update_callback_counter
|
94
|
+
self.update_callback_count ||= 0
|
95
|
+
self.update_callback_count = self.update_callback_count + 1
|
96
|
+
end
|
100
97
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
98
|
+
def increment_validation_counter
|
99
|
+
self.validation_count ||= 0
|
100
|
+
self.validation_count = self.validation_count + 1
|
101
|
+
end
|
105
102
|
end
|
metadata
CHANGED
@@ -1,57 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: destroyed_at
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Dupuis Jr.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 4.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: actionpack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
33
|
+
version: 4.0.0
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 4.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '1.3'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
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'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: minitest
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +159,7 @@ extra_rdoc_files: []
|
|
145
159
|
files:
|
146
160
|
- .gitignore
|
147
161
|
- .travis.yml
|
162
|
+
- CHANGELOG.md
|
148
163
|
- CONTRIBUTING.md
|
149
164
|
- Gemfile
|
150
165
|
- README.md
|
@@ -155,8 +170,12 @@ files:
|
|
155
170
|
- lib/destroyed_at/belongs_to_association.rb
|
156
171
|
- lib/destroyed_at/has_many_association.rb
|
157
172
|
- lib/destroyed_at/has_one_association.rb
|
173
|
+
- lib/destroyed_at/mapper.rb
|
174
|
+
- lib/destroyed_at/routes.rb
|
158
175
|
- lib/destroyed_at/version.rb
|
159
176
|
- test/destroyed_at_test.rb
|
177
|
+
- test/mapper_test.rb
|
178
|
+
- test/scope_test.rb
|
160
179
|
- test/test_helper.rb
|
161
180
|
homepage: https://github.com/dockyard/destroyed_at
|
162
181
|
licenses:
|
@@ -178,10 +197,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
197
|
version: '0'
|
179
198
|
requirements: []
|
180
199
|
rubyforge_project:
|
181
|
-
rubygems_version: 2.
|
200
|
+
rubygems_version: 2.0.3
|
182
201
|
signing_key:
|
183
202
|
specification_version: 4
|
184
203
|
summary: Safe destroy for ActiveRecord.
|
185
204
|
test_files:
|
186
205
|
- test/destroyed_at_test.rb
|
206
|
+
- test/mapper_test.rb
|
207
|
+
- test/scope_test.rb
|
187
208
|
- test/test_helper.rb
|
209
|
+
has_rdoc:
|