discard 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +17 -7
- data/.yardopts +8 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +4 -0
- data/README.md +36 -17
- data/Rakefile +6 -7
- data/discard.gemspec +4 -3
- data/lib/discard/errors.rb +29 -0
- data/lib/discard/model.rb +89 -20
- data/lib/discard/version.rb +4 -1
- data/lib/discard.rb +3 -0
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2418547a70d311cc7d970ce492f505a0807db23a03eebe2ae1e1f7589a0edb8b
|
4
|
+
data.tar.gz: a45947c018416ee63a873fc0f94f143f20a6a5e34892420893bf3fe50b9a5406
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4111df84d2628abc622a64bcb730ef1acc1b5d509ed2c226a5f5b5ca96fecaaebf65bf39fa2f54392c9161198e496fd84aa78f72aebf921c21c9c3cdd18ac687
|
7
|
+
data.tar.gz: a881fee07ba21fbf6281f966ff51607a5f2acfb127a43fd35e3a9bfbc2c2dcdfec1962e14ba475e2c33dae81e0dca56f5de8172c4afe3e9536e5f36377d33b82
|
data/.travis.yml
CHANGED
@@ -1,14 +1,24 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
-
- 2.2.
|
5
|
-
- 2.3.
|
6
|
-
- 2.4.
|
7
|
-
- 2.5.
|
4
|
+
- 2.2.10
|
5
|
+
- 2.3.8
|
6
|
+
- 2.4.6
|
7
|
+
- 2.5.5
|
8
|
+
- 2.6.3
|
8
9
|
env:
|
9
10
|
matrix:
|
10
|
-
- RAILS_VERSION='~> 4.2.0'
|
11
|
-
- RAILS_VERSION='~> 5.0.0'
|
11
|
+
- RAILS_VERSION='~> 4.2.0' SQLITE_VERSION='~> 1.3.6'
|
12
|
+
- RAILS_VERSION='~> 5.0.0' SQLITE_VERSION='~> 1.3.6'
|
12
13
|
- RAILS_VERSION='~> 5.1.0'
|
13
14
|
- RAILS_VERSION='~> 5.2.x'
|
14
|
-
|
15
|
+
- RAILS_VERSION='~> 6.0.0.beta3'
|
16
|
+
|
17
|
+
matrix:
|
18
|
+
exclude:
|
19
|
+
- rvm: 2.2.10
|
20
|
+
env: RAILS_VERSION='~> 6.0.0.beta3'
|
21
|
+
- rvm: 2.3.8
|
22
|
+
env: RAILS_VERSION='~> 6.0.0.beta3'
|
23
|
+
- rvm: 2.4.6
|
24
|
+
env: RAILS_VERSION='~> 6.0.0.beta3'
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
### Unreleased
|
2
|
+
|
3
|
+
### Version 1.1.0
|
4
|
+
Release date: 2019-05-03
|
5
|
+
|
6
|
+
* Support for ActiveRecord 6
|
7
|
+
* `discard_all` and `undiscard_all` now return affected records
|
8
|
+
* Add `discard!` and `undiscard!`
|
9
|
+
|
10
|
+
### Version 1.0.0
|
11
|
+
Release date: 2018-03-16
|
12
|
+
|
13
|
+
* Add undiscard callbacks and `.undiscard_all`
|
14
|
+
|
15
|
+
### Version 0.2.0
|
16
|
+
Release date: 2017-11-22
|
17
|
+
|
18
|
+
* Add `.discard_all`
|
19
|
+
* Add `undiscarded` scope
|
20
|
+
* Add callbacks
|
21
|
+
|
22
|
+
### Version 0.1.0
|
23
|
+
Release date: 2017-04-28
|
24
|
+
|
25
|
+
* Initial version!
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -32,10 +32,15 @@ class Post < ActiveRecord::Base
|
|
32
32
|
end
|
33
33
|
```
|
34
34
|
|
35
|
+
You can either generate a migration using:
|
36
|
+
```
|
37
|
+
rails generate migration add_discarded_at_to_posts discarded_at:datetime:index
|
38
|
+
```
|
35
39
|
|
40
|
+
or create one yourself like the one below:
|
36
41
|
``` ruby
|
37
42
|
class AddDiscardToPosts < ActiveRecord::Migration[5.0]
|
38
|
-
def
|
43
|
+
def change
|
39
44
|
add_column :posts, :discarded_at, :datetime
|
40
45
|
add_index :posts, :discarded_at
|
41
46
|
end
|
@@ -43,9 +48,9 @@ end
|
|
43
48
|
```
|
44
49
|
|
45
50
|
|
46
|
-
|
51
|
+
#### Discard a record
|
47
52
|
|
48
|
-
```
|
53
|
+
```ruby
|
49
54
|
Post.all # => [#<Post id: 1, ...>]
|
50
55
|
Post.kept # => [#<Post id: 1, ...>]
|
51
56
|
Post.discarded # => []
|
@@ -60,7 +65,7 @@ Post.kept # => []
|
|
60
65
|
Post.discarded # => [#<Post id: 1, ...>]
|
61
66
|
```
|
62
67
|
|
63
|
-
|
68
|
+
***From a controller***
|
64
69
|
|
65
70
|
Controller actions need a small modification to discard records instead of deleting them. Just replace `destroy` with `discard`.
|
66
71
|
|
@@ -72,23 +77,23 @@ end
|
|
72
77
|
```
|
73
78
|
|
74
79
|
|
75
|
-
|
80
|
+
#### Undiscard a record
|
76
81
|
|
77
|
-
```
|
82
|
+
```ruby
|
78
83
|
post = Post.first # => #<Post id: 1, ...>
|
79
84
|
post.undiscard # => true
|
80
85
|
```
|
81
86
|
|
82
87
|
***From a controller***
|
83
88
|
|
84
|
-
```
|
89
|
+
```ruby
|
85
90
|
def update
|
86
91
|
@post.undiscard
|
87
92
|
redirect_to users_url, notice: "Post undiscarded"
|
88
93
|
end
|
89
94
|
```
|
90
95
|
|
91
|
-
|
96
|
+
#### Working with associations
|
92
97
|
|
93
98
|
Under paranoia, soft deleting a record will destroy any `dependent: :destroy`
|
94
99
|
associations. Probably not what you want! This leads to all dependent records
|
@@ -114,10 +119,10 @@ discarded. Just override the `kept` scope on the Comment model.
|
|
114
119
|
|
115
120
|
``` ruby
|
116
121
|
class Comment < ActiveRecord::Base
|
117
|
-
|
122
|
+
belongs_to :post
|
118
123
|
|
119
124
|
include Discard::Model
|
120
|
-
scope :kept, -> { undiscarded.joins(:
|
125
|
+
scope :kept, -> { undiscarded.joins(:post).merge(Post.kept) }
|
121
126
|
end
|
122
127
|
|
123
128
|
Comment.kept
|
@@ -133,7 +138,7 @@ SQL databases are very good at this, and performance should not be an issue.
|
|
133
138
|
In both of these cases restoring either of these records will do right thing!
|
134
139
|
|
135
140
|
|
136
|
-
|
141
|
+
#### Default scope
|
137
142
|
|
138
143
|
It's usually undesirable to add a default scope. It will take more effort to
|
139
144
|
work around and will cause more headaches. If you know you need a default scope, it's easy to add yourself ❤.
|
@@ -149,7 +154,7 @@ Post.with_discarded # All Posts
|
|
149
154
|
Post.with_discarded.discarded # Only discarded posts
|
150
155
|
```
|
151
156
|
|
152
|
-
|
157
|
+
#### Custom column
|
153
158
|
|
154
159
|
If you're migrating from paranoia, you might want to continue using the same
|
155
160
|
column.
|
@@ -161,12 +166,16 @@ class Post < ActiveRecord::Base
|
|
161
166
|
end
|
162
167
|
```
|
163
168
|
|
164
|
-
|
169
|
+
#### Callbacks
|
165
170
|
|
166
171
|
Callbacks can be run before, after, or around the discard and undiscard operations.
|
167
172
|
A likely use is discarding or deleting associated records (but see "Working with associations" for an alternative).
|
168
173
|
|
169
174
|
``` ruby
|
175
|
+
class Comment < ActiveRecord::Base
|
176
|
+
include Discard::Model
|
177
|
+
end
|
178
|
+
|
170
179
|
class Post < ActiveRecord::Base
|
171
180
|
include Discard::Model
|
172
181
|
|
@@ -182,15 +191,25 @@ class Post < ActiveRecord::Base
|
|
182
191
|
end
|
183
192
|
```
|
184
193
|
|
185
|
-
|
194
|
+
*Warning:* Please note that callbacks for save and update are run when discarding/undiscarding a record
|
195
|
+
|
196
|
+
|
197
|
+
#### Performance tuning
|
198
|
+
`discard_all` and `undiscard_all` is intended to behave like `destroy_all` which has callbacks, validations, and does one query per record. If performance is a big concern, you may consider replacing it with:
|
199
|
+
|
200
|
+
`scope.update_all(discarded_at: Time.current)`
|
201
|
+
or
|
202
|
+
`scope.update_all(discarded_at: nil)`
|
203
|
+
|
204
|
+
#### Working with Devise
|
186
205
|
|
187
206
|
A common use case is to apply discard to a User record. Even though a user has been discarded they can still login and continue their session.
|
188
207
|
If you are using Devise and wish for discarded users to be unable to login and stop their session you can override Devise's method.
|
189
208
|
|
190
|
-
```
|
209
|
+
```ruby
|
191
210
|
class User < ActiveRecord::Base
|
192
211
|
def active_for_authentication?
|
193
|
-
super && !
|
212
|
+
super && !discarded?
|
194
213
|
end
|
195
214
|
end
|
196
215
|
```
|
@@ -217,7 +236,7 @@ and awkward behaviour.
|
|
217
236
|
adding `.with_deleted` to associations or anywhere soft-deleted records
|
218
237
|
should be found. :disappointed:
|
219
238
|
* Adding `belongs_to :child, -> { with_deleted }` helps, but doesn't work for
|
220
|
-
joins and eager-loading.
|
239
|
+
joins and eager-loading [before Rails 5.2](https://github.com/rubysherpas/paranoia/issues/355)
|
221
240
|
* `delete` is overridden (`really_delete` will actually delete the record) :unamused:
|
222
241
|
* `destroy` is overridden (`really_destroy` will actually delete the record) :pensive:
|
223
242
|
* `dependent: :destroy` associations are deleted when performing soft-destroys :scream:
|
data/Rakefile
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
2
4
|
|
3
|
-
|
4
|
-
require 'rspec/core/rake_task'
|
5
|
-
RSpec::Core::RakeTask.new(:spec)
|
6
|
-
rescue LoadError
|
7
|
-
end
|
5
|
+
RSpec::Core::RakeTask.new(:rspec)
|
8
6
|
|
9
|
-
|
7
|
+
desc 'Run the test suite'
|
8
|
+
task default: :rspec
|
data/discard.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'discard/version'
|
@@ -21,8 +22,8 @@ Gem::Specification.new do |spec|
|
|
21
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
23
|
spec.require_paths = ["lib"]
|
23
24
|
|
24
|
-
spec.add_dependency "activerecord",
|
25
|
-
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_dependency "activerecord", ">= 4.2", "< 7"
|
26
|
+
spec.add_development_dependency "bundler"
|
26
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.5.0"
|
28
29
|
spec.add_development_dependency "database_cleaner", "~> 1.5"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discard
|
4
|
+
# = Discard Errors
|
5
|
+
#
|
6
|
+
# Generic exception class.
|
7
|
+
class DiscardError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Raised by {Discard::Model#discard!}
|
11
|
+
class RecordNotDiscarded < DiscardError
|
12
|
+
attr_reader :record
|
13
|
+
|
14
|
+
def initialize(message = nil, record = nil)
|
15
|
+
@record = record
|
16
|
+
super(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raised by {Discard::Model#undiscard!}
|
21
|
+
class RecordNotUndiscarded < DiscardError
|
22
|
+
attr_reader :record
|
23
|
+
|
24
|
+
def initialize(message = nil, record = nil)
|
25
|
+
@record = record
|
26
|
+
super(message)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/discard/model.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Discard
|
4
|
+
# Handles soft deletes of records.
|
5
|
+
#
|
6
|
+
# Options:
|
7
|
+
#
|
8
|
+
# - :discard_column - The columns used to track soft delete, defaults to `:discarded_at`.
|
2
9
|
module Model
|
3
10
|
extend ActiveSupport::Concern
|
4
11
|
|
@@ -15,42 +22,104 @@ module Discard
|
|
15
22
|
define_model_callbacks :undiscard
|
16
23
|
end
|
17
24
|
|
25
|
+
# :nodoc:
|
18
26
|
module ClassMethods
|
27
|
+
# Discards the records by instantiating each
|
28
|
+
# record and calling its {#discard} method.
|
29
|
+
# Each object's callbacks are executed.
|
30
|
+
# Returns the collection of objects that were discarded.
|
31
|
+
#
|
32
|
+
# Note: Instantiation, callback execution, and update of each
|
33
|
+
# record can be time consuming when you're discarding many records at
|
34
|
+
# once. It generates at least one SQL +UPDATE+ query per record (or
|
35
|
+
# possibly more, to enforce your callbacks). If you want to discard many
|
36
|
+
# rows quickly, without concern for their associations or callbacks, use
|
37
|
+
# #update_all(discarded_at: Time.current) instead.
|
38
|
+
#
|
39
|
+
# ==== Examples
|
40
|
+
#
|
41
|
+
# Person.where(age: 0..18).discard_all
|
19
42
|
def discard_all
|
20
|
-
|
43
|
+
kept.each(&:discard)
|
21
44
|
end
|
45
|
+
|
46
|
+
# Undiscards the records by instantiating each
|
47
|
+
# record and calling its {#undiscard} method.
|
48
|
+
# Each object's callbacks are executed.
|
49
|
+
# Returns the collection of objects that were undiscarded.
|
50
|
+
#
|
51
|
+
# Note: Instantiation, callback execution, and update of each
|
52
|
+
# record can be time consuming when you're undiscarding many records at
|
53
|
+
# once. It generates at least one SQL +UPDATE+ query per record (or
|
54
|
+
# possibly more, to enforce your callbacks). If you want to undiscard many
|
55
|
+
# rows quickly, without concern for their associations or callbacks, use
|
56
|
+
# #update_all(discarded_at: nil) instead.
|
57
|
+
#
|
58
|
+
# ==== Examples
|
59
|
+
#
|
60
|
+
# Person.where(age: 0..18).undiscard_all
|
22
61
|
def undiscard_all
|
23
|
-
|
62
|
+
discarded.each(&:undiscard)
|
24
63
|
end
|
25
64
|
end
|
26
65
|
|
27
|
-
# @return [
|
66
|
+
# @return [Boolean] true if this record has been discarded, otherwise false
|
28
67
|
def discarded?
|
29
|
-
|
68
|
+
self[self.class.discard_column].present?
|
30
69
|
end
|
31
70
|
|
32
|
-
#
|
71
|
+
# Discard the record in the database
|
72
|
+
#
|
73
|
+
# @return [Boolean] true if successful, otherwise false
|
33
74
|
def discard
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
self[self.class.discard_column] = Time.current
|
38
|
-
save
|
39
|
-
end
|
40
|
-
end
|
75
|
+
return if discarded?
|
76
|
+
run_callbacks(:discard) do
|
77
|
+
update_attribute(self.class.discard_column, Time.current)
|
41
78
|
end
|
42
79
|
end
|
43
80
|
|
44
|
-
#
|
81
|
+
# Discard the record in the database
|
82
|
+
#
|
83
|
+
# There's a series of callbacks associated with #discard!. If the
|
84
|
+
# <tt>before_discard</tt> callback throws +:abort+ the action is cancelled
|
85
|
+
# and #discard! raises {Discard::RecordNotDiscarded}.
|
86
|
+
#
|
87
|
+
# @return [Boolean] true if successful
|
88
|
+
# @raise {Discard::RecordNotDiscarded}
|
89
|
+
def discard!
|
90
|
+
discard || _raise_record_not_discarded
|
91
|
+
end
|
92
|
+
|
93
|
+
# Undiscard the record in the database
|
94
|
+
#
|
95
|
+
# @return [Boolean] true if successful, otherwise false
|
45
96
|
def undiscard
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
self[self.class.discard_column] = nil
|
50
|
-
save
|
51
|
-
end
|
52
|
-
end
|
97
|
+
return unless discarded?
|
98
|
+
run_callbacks(:undiscard) do
|
99
|
+
update_attribute(self.class.discard_column, nil)
|
53
100
|
end
|
54
101
|
end
|
102
|
+
|
103
|
+
# Discard the record in the database
|
104
|
+
#
|
105
|
+
# There's a series of callbacks associated with #undiscard!. If the
|
106
|
+
# <tt>before_undiscard</tt> callback throws +:abort+ the action is cancelled
|
107
|
+
# and #undiscard! raises {Discard::RecordNotUndiscarded}.
|
108
|
+
#
|
109
|
+
# @return [Boolean] true if successful
|
110
|
+
# @raise {Discard::RecordNotUndiscarded}
|
111
|
+
def undiscard!
|
112
|
+
undiscard || _raise_record_not_undiscarded
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def _raise_record_not_discarded
|
118
|
+
raise ::Discard::RecordNotDiscarded.new("Failed to discard the record", self)
|
119
|
+
end
|
120
|
+
|
121
|
+
def _raise_record_not_undiscarded
|
122
|
+
raise ::Discard::RecordNotUndiscarded.new("Failed to undiscard the record", self)
|
123
|
+
end
|
55
124
|
end
|
56
125
|
end
|
data/lib/discard/version.rb
CHANGED
data/lib/discard.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-05-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '4.2'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '7'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,21 +29,21 @@ dependencies:
|
|
29
29
|
version: '4.2'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '7'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: bundler
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
39
|
+
version: '0'
|
40
40
|
type: :development
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
46
|
+
version: '0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,6 +125,8 @@ files:
|
|
125
125
|
- ".gitignore"
|
126
126
|
- ".rspec"
|
127
127
|
- ".travis.yml"
|
128
|
+
- ".yardopts"
|
129
|
+
- CHANGELOG.md
|
128
130
|
- CODE_OF_CONDUCT.md
|
129
131
|
- Gemfile
|
130
132
|
- LICENSE.txt
|
@@ -134,6 +136,7 @@ files:
|
|
134
136
|
- bin/setup
|
135
137
|
- discard.gemspec
|
136
138
|
- lib/discard.rb
|
139
|
+
- lib/discard/errors.rb
|
137
140
|
- lib/discard/model.rb
|
138
141
|
- lib/discard/version.rb
|
139
142
|
homepage: https://github.com/jhawthorn/discard
|
@@ -155,8 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
158
|
- !ruby/object:Gem::Version
|
156
159
|
version: '0'
|
157
160
|
requirements: []
|
158
|
-
|
159
|
-
rubygems_version: 2.7.6
|
161
|
+
rubygems_version: 3.0.1
|
160
162
|
signing_key:
|
161
163
|
specification_version: 4
|
162
164
|
summary: ActiveRecord soft-deletes done right
|