discard 0.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE/bug-report.md +24 -0
- data/.github/ISSUE_TEMPLATE/feature-proposal.md +10 -0
- data/.github/workflows/test.yml +32 -0
- data/.travis.yml +24 -8
- data/.yardopts +8 -0
- data/CHANGELOG.md +36 -0
- data/Gemfile +4 -0
- data/README.md +138 -48
- data/Rakefile +6 -7
- data/discard.gemspec +4 -3
- data/lib/discard/errors.rb +29 -0
- data/lib/discard/model.rb +138 -12
- data/lib/discard/version.rb +4 -1
- data/lib/discard.rb +3 -0
- metadata +18 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27523357b203abf0f0928633448c778ec25ca89f36a9ec2e8178149b9a37ab67
|
4
|
+
data.tar.gz: 0650603e1aa80be6727824df9b74486fa8ad7af81f44074bf4c6db7b5b7c88cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e77b001ed871ddee9dcc0de5379b15c13370d5976b050cc1690dfd03d99898c6cde6993660bf379fc9d0c01e489655ec1b7d5d2f12bfd8176b9e68fbacc59841
|
7
|
+
data.tar.gz: d1e59a758e632ff4bfb96ae854e6b79eeb9a6eba76a6d6935f2f6fcad3c7f23fb4ee74e6562dc5055d89ddbc361dcab76440e5d5fa7f3ca6615513c3f763f9c9
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
name: Bug Report
|
3
|
+
about: Is Discard not working correctly for you? Let us know!
|
4
|
+
title: ''
|
5
|
+
labels: ''
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Describe the bug**
|
11
|
+
A clear and concise description of what the bug is.
|
12
|
+
|
13
|
+
**To Reproduce**
|
14
|
+
Steps to reproduce the behavior:
|
15
|
+
1. Go to '...'
|
16
|
+
2. Click on '....'
|
17
|
+
3. Scroll down to '....'
|
18
|
+
4. See error
|
19
|
+
|
20
|
+
**Expected behavior**
|
21
|
+
A clear and concise description of what you expected to happen.
|
22
|
+
|
23
|
+
**Additional context**
|
24
|
+
Please tells us your Rails and Ruby versions, and anything else that might be helpful about the environment you encountered the issue.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
---
|
2
|
+
name: Feature Proposal
|
3
|
+
about: Discard is feature-complete, but we're happy to hear you out!
|
4
|
+
title: ''
|
5
|
+
labels: ''
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
**Discard is feature-complete, but if you have an idea for a feature that will benefit all discard users and that won't offer a significant maintenance burden, we're happy to listen. Explain it as best you can and we'll let you know if it's something we'd like to have or if it's something that might be a better off as an extension or fork.**
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- master
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
build:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
name: Test on Rails ${{ matrix.rails_version }}
|
15
|
+
strategy:
|
16
|
+
matrix:
|
17
|
+
rails_version:
|
18
|
+
- ~> 5.1.0
|
19
|
+
- ~> 5.2.0
|
20
|
+
- ~> 6.0.0
|
21
|
+
- ~> 6.1.0
|
22
|
+
- ~> 7.0.0
|
23
|
+
container: docker://ruby
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v1
|
26
|
+
- name: Build and test
|
27
|
+
run: |
|
28
|
+
gem install bundler
|
29
|
+
bundle install --jobs 4 --retry 3
|
30
|
+
bundle exec rake
|
31
|
+
env:
|
32
|
+
RAILS_VERSION: ${{ matrix.rails_version }}
|
data/.travis.yml
CHANGED
@@ -1,12 +1,28 @@
|
|
1
|
-
sudo: false
|
2
1
|
language: ruby
|
2
|
+
cache: bundler
|
3
3
|
rvm:
|
4
|
-
- 2.2
|
5
|
-
- 2.3
|
6
|
-
- 2.4
|
4
|
+
- 2.2
|
5
|
+
- 2.3
|
6
|
+
- 2.4
|
7
|
+
- 2.5
|
8
|
+
- 2.6
|
9
|
+
- 2.7
|
7
10
|
env:
|
8
11
|
matrix:
|
9
|
-
- RAILS_VERSION='~> 4.2.0'
|
10
|
-
- RAILS_VERSION='~> 5.0.0'
|
11
|
-
- RAILS_VERSION='~> 5.1.
|
12
|
-
|
12
|
+
- RAILS_VERSION='~> 4.2.0' SQLITE_VERSION='~> 1.3.6'
|
13
|
+
- RAILS_VERSION='~> 5.0.0' SQLITE_VERSION='~> 1.3.6'
|
14
|
+
- RAILS_VERSION='~> 5.1.0'
|
15
|
+
- RAILS_VERSION='~> 5.2.0'
|
16
|
+
- RAILS_VERSION='~> 6.0.0'
|
17
|
+
|
18
|
+
matrix:
|
19
|
+
exclude:
|
20
|
+
- rvm: 2.2
|
21
|
+
env: RAILS_VERSION='~> 6.0.0'
|
22
|
+
- rvm: 2.3
|
23
|
+
env: RAILS_VERSION='~> 6.0.0'
|
24
|
+
- rvm: 2.4
|
25
|
+
env: RAILS_VERSION='~> 6.0.0'
|
26
|
+
# Rails 4.2 uses BigDecimal.new, which Ruby 2.7 removed
|
27
|
+
- rvm: 2.7
|
28
|
+
env: RAILS_VERSION='~> 4.2.0' SQLITE_VERSION='~> 1.3.6'
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
### Unreleased
|
2
|
+
|
3
|
+
### Version 1.2.1
|
4
|
+
Release date: 2021-12-16
|
5
|
+
|
6
|
+
* Support for ActiveRecord 7
|
7
|
+
|
8
|
+
### Version 1.2.0
|
9
|
+
Release date: 2020-02-17
|
10
|
+
|
11
|
+
* Add `discard_all!` and `undiscard_all!`
|
12
|
+
* Add `undiscarded?` and `kept?` to match the scopes of the same names
|
13
|
+
|
14
|
+
### Version 1.1.0
|
15
|
+
Release date: 2019-05-03
|
16
|
+
|
17
|
+
* Support for ActiveRecord 6
|
18
|
+
* `discard_all` and `undiscard_all` now return affected records
|
19
|
+
* Add `discard!` and `undiscard!`
|
20
|
+
|
21
|
+
### Version 1.0.0
|
22
|
+
Release date: 2018-03-16
|
23
|
+
|
24
|
+
* Add undiscard callbacks and `.undiscard_all`
|
25
|
+
|
26
|
+
### Version 0.2.0
|
27
|
+
Release date: 2017-11-22
|
28
|
+
|
29
|
+
* Add `.discard_all`
|
30
|
+
* Add `undiscarded` scope
|
31
|
+
* Add callbacks
|
32
|
+
|
33
|
+
### Version 0.1.0
|
34
|
+
Release date: 2017-04-28
|
35
|
+
|
36
|
+
* Initial version!
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -8,42 +8,12 @@ Soft deletes for ActiveRecord done right.
|
|
8
8
|
|
9
9
|
A simple ActiveRecord mixin to add conventions for flagging records as discarded.
|
10
10
|
|
11
|
-
## Why should I use this?
|
12
|
-
|
13
|
-
I've worked with and have helped maintain
|
14
|
-
[paranoia](https://github.com/rubysherpas/paranoia) for a while. I'm convinced
|
15
|
-
it does the wrong thing for most cases.
|
16
|
-
|
17
|
-
Paranoia and
|
18
|
-
[acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) both
|
19
|
-
attempt to emulate deletes by setting a column and adding a default scope on the
|
20
|
-
model. This requires some ActiveRecord hackery, and leads to some surprising
|
21
|
-
and awkward behaviour.
|
22
|
-
|
23
|
-
* A default scope is added to hide soft-deleted records, which necessitates
|
24
|
-
adding `.with_deleted` to associations or anywhere soft-deleted records
|
25
|
-
should be found. :disappointed:
|
26
|
-
* Adding `belongs_to :child, -> { with_deleted }` helps, but doesn't work for
|
27
|
-
joins and eager-loading.
|
28
|
-
* `delete` is overridden (`really_delete` will actually delete the record) :unamused:
|
29
|
-
* `destroy` is overridden (`really_destroy` will actually delete the record) :pensive:
|
30
|
-
* `dependent: :destroy` associations are deleted when performing soft-destroys :scream:
|
31
|
-
* requiring any dependent records to also be `acts_as_paranoid` to avoid losing data. :grimacing:
|
32
|
-
|
33
|
-
There are some use cases where these behaviours make sense: if you really did
|
34
|
-
want to _almost_ delete the record. More often developers are just looking to
|
35
|
-
hide some records, or mark them as inactive.
|
36
|
-
|
37
|
-
Discard takes a different approach. It doesn't override any ActiveRecord
|
38
|
-
methods and instead simply provides convenience methods and scopes for
|
39
|
-
discarding (hiding), restoring, and querying records.
|
40
|
-
|
41
11
|
## Installation
|
42
12
|
|
43
13
|
Add this line to your application's Gemfile:
|
44
14
|
|
45
15
|
```ruby
|
46
|
-
gem 'discard'
|
16
|
+
gem 'discard', '~> 1.2'
|
47
17
|
```
|
48
18
|
|
49
19
|
And then execute:
|
@@ -62,33 +32,43 @@ class Post < ActiveRecord::Base
|
|
62
32
|
end
|
63
33
|
```
|
64
34
|
|
35
|
+
You can either generate a migration using:
|
36
|
+
```
|
37
|
+
rails generate migration add_discarded_at_to_posts discarded_at:datetime:index
|
38
|
+
```
|
65
39
|
|
40
|
+
or create one yourself like the one below:
|
66
41
|
``` ruby
|
67
42
|
class AddDiscardToPosts < ActiveRecord::Migration[5.0]
|
68
|
-
def
|
43
|
+
def change
|
69
44
|
add_column :posts, :discarded_at, :datetime
|
45
|
+
add_index :posts, :discarded_at
|
70
46
|
end
|
71
47
|
end
|
72
48
|
```
|
73
49
|
|
74
50
|
|
75
|
-
|
76
|
-
|
51
|
+
#### Discard a record
|
52
|
+
|
53
|
+
```ruby
|
77
54
|
Post.all # => [#<Post id: 1, ...>]
|
78
55
|
Post.kept # => [#<Post id: 1, ...>]
|
79
56
|
Post.discarded # => []
|
80
57
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
58
|
+
post = Post.first # => #<Post id: 1, ...>
|
59
|
+
post.discard # => true
|
60
|
+
post.discard! # => Discard::RecordNotDiscarded: Failed to discard the record
|
61
|
+
post.discarded? # => true
|
62
|
+
post.undiscarded? # => false
|
63
|
+
post.kept? # => false
|
64
|
+
post.discarded_at # => 2017-04-18 18:49:49 -0700
|
85
65
|
|
86
66
|
Post.all # => [#<Post id: 1, ...>]
|
87
67
|
Post.kept # => []
|
88
68
|
Post.discarded # => [#<Post id: 1, ...>]
|
89
69
|
```
|
90
70
|
|
91
|
-
|
71
|
+
***From a controller***
|
92
72
|
|
93
73
|
Controller actions need a small modification to discard records instead of deleting them. Just replace `destroy` with `discard`.
|
94
74
|
|
@@ -99,7 +79,26 @@ def destroy
|
|
99
79
|
end
|
100
80
|
```
|
101
81
|
|
102
|
-
|
82
|
+
|
83
|
+
#### Undiscard a record
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
post = Post.first # => #<Post id: 1, ...>
|
87
|
+
post.undiscard # => true
|
88
|
+
post.undiscard! # => Discard::RecordNotUndiscarded: Failed to undiscard the record
|
89
|
+
post.discarded_at # => nil
|
90
|
+
```
|
91
|
+
|
92
|
+
***From a controller***
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
def update
|
96
|
+
@post.undiscard
|
97
|
+
redirect_to users_url, notice: "Post undiscarded"
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
#### Working with associations
|
103
102
|
|
104
103
|
Under paranoia, soft deleting a record will destroy any `dependent: :destroy`
|
105
104
|
associations. Probably not what you want! This leads to all dependent records
|
@@ -125,10 +124,14 @@ discarded. Just override the `kept` scope on the Comment model.
|
|
125
124
|
|
126
125
|
``` ruby
|
127
126
|
class Comment < ActiveRecord::Base
|
128
|
-
|
127
|
+
belongs_to :post
|
129
128
|
|
130
129
|
include Discard::Model
|
131
|
-
scope :kept, -> { undiscarded.joins(:
|
130
|
+
scope :kept, -> { undiscarded.joins(:post).merge(Post.kept) }
|
131
|
+
|
132
|
+
def kept?
|
133
|
+
undiscarded? && post.kept?
|
134
|
+
end
|
132
135
|
end
|
133
136
|
|
134
137
|
Comment.kept
|
@@ -144,7 +147,7 @@ SQL databases are very good at this, and performance should not be an issue.
|
|
144
147
|
In both of these cases restoring either of these records will do right thing!
|
145
148
|
|
146
149
|
|
147
|
-
|
150
|
+
#### Default scope
|
148
151
|
|
149
152
|
It's usually undesirable to add a default scope. It will take more effort to
|
150
153
|
work around and will cause more headaches. If you know you need a default scope, it's easy to add yourself ❤.
|
@@ -160,7 +163,7 @@ Post.with_discarded # All Posts
|
|
160
163
|
Post.with_discarded.discarded # Only discarded posts
|
161
164
|
```
|
162
165
|
|
163
|
-
|
166
|
+
#### Custom column
|
164
167
|
|
165
168
|
If you're migrating from paranoia, you might want to continue using the same
|
166
169
|
column.
|
@@ -172,12 +175,95 @@ class Post < ActiveRecord::Base
|
|
172
175
|
end
|
173
176
|
```
|
174
177
|
|
178
|
+
#### Callbacks
|
179
|
+
|
180
|
+
Callbacks can be run before, after, or around the discard and undiscard operations.
|
181
|
+
A likely use is discarding or deleting associated records (but see "Working with associations" for an alternative).
|
182
|
+
|
183
|
+
``` ruby
|
184
|
+
class Comment < ActiveRecord::Base
|
185
|
+
include Discard::Model
|
186
|
+
end
|
187
|
+
|
188
|
+
class Post < ActiveRecord::Base
|
189
|
+
include Discard::Model
|
190
|
+
|
191
|
+
has_many :comments
|
192
|
+
|
193
|
+
after_discard do
|
194
|
+
comments.discard_all
|
195
|
+
end
|
196
|
+
|
197
|
+
after_undiscard do
|
198
|
+
comments.undiscard_all
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
*Warning:* Please note that callbacks for save and update are run when discarding/undiscarding a record
|
204
|
+
|
205
|
+
|
206
|
+
#### Performance tuning
|
207
|
+
`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:
|
208
|
+
|
209
|
+
`scope.update_all(discarded_at: Time.current)`
|
210
|
+
or
|
211
|
+
`scope.update_all(discarded_at: nil)`
|
212
|
+
|
213
|
+
#### Working with Devise
|
214
|
+
|
215
|
+
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.
|
216
|
+
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.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
class User < ActiveRecord::Base
|
220
|
+
def active_for_authentication?
|
221
|
+
super && !discarded?
|
222
|
+
end
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
175
226
|
## Non-features
|
176
227
|
|
177
|
-
* Restoring records (this will probably be added)
|
178
|
-
* Discarding dependent records (this will likely be added)
|
179
|
-
* Callbacks (this will probably be added)
|
180
228
|
* Special handling of AR counter cache columns - The counter cache counts the total number of records, both kept and discarded.
|
229
|
+
* Recursive discards (like AR's dependent: destroy) - This can be avoided using queries (See "Working with associations") or emulated using callbacks.
|
230
|
+
* Recursive restores - This concept is fundamentally broken, but not necessary if the recursive discards are avoided.
|
231
|
+
|
232
|
+
## Extensions
|
233
|
+
|
234
|
+
Discard provides the smallest subset of soft-deletion features that we think are useful to all users of the gem. We welcome the addition of gems that work with Discard to provide additional features.
|
235
|
+
|
236
|
+
- [discard-rails-observers](https://github.com/pelargir/discard-rails-observers) integrates discard with the [rails-observers gem](https://github.com/rails/rails-observers)
|
237
|
+
|
238
|
+
## Why not paranoia or acts_as_paranoid?
|
239
|
+
|
240
|
+
I've worked with and have helped maintain
|
241
|
+
[paranoia](https://github.com/rubysherpas/paranoia) for a while. I'm convinced
|
242
|
+
it does the wrong thing for most cases.
|
243
|
+
|
244
|
+
Paranoia and
|
245
|
+
[acts_as_paranoid](https://github.com/ActsAsParanoid/acts_as_paranoid) both
|
246
|
+
attempt to emulate deletes by setting a column and adding a default scope on the
|
247
|
+
model. This requires some ActiveRecord hackery, and leads to some surprising
|
248
|
+
and awkward behaviour.
|
249
|
+
|
250
|
+
* A default scope is added to hide soft-deleted records, which necessitates
|
251
|
+
adding `.with_deleted` to associations or anywhere soft-deleted records
|
252
|
+
should be found. :disappointed:
|
253
|
+
* Adding `belongs_to :child, -> { with_deleted }` helps, but doesn't work for
|
254
|
+
joins and eager-loading [before Rails 5.2](https://github.com/rubysherpas/paranoia/issues/355)
|
255
|
+
* `delete` is overridden (`really_delete` will actually delete the record) :unamused:
|
256
|
+
* `destroy` is overridden (`really_destroy` will actually delete the record) :pensive:
|
257
|
+
* `dependent: :destroy` associations are deleted when performing soft-destroys :scream:
|
258
|
+
* requiring any dependent records to also be `acts_as_paranoid` to avoid losing data. :grimacing:
|
259
|
+
|
260
|
+
There are some use cases where these behaviours make sense: if you really did
|
261
|
+
want to _almost_ delete the record. More often developers are just looking to
|
262
|
+
hide some records, or mark them as inactive.
|
263
|
+
|
264
|
+
Discard takes a different approach. It doesn't override any ActiveRecord
|
265
|
+
methods and instead simply provides convenience methods and scopes for
|
266
|
+
discarding (hiding), restoring, and querying records.
|
181
267
|
|
182
268
|
## Development
|
183
269
|
|
@@ -185,7 +271,11 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
185
271
|
|
186
272
|
## Contributing
|
187
273
|
|
188
|
-
|
274
|
+
Please consider filing an issue with the details of any features you'd like to see before implementing them. Discard is feature-complete and we are only interested in adding additional features that won't require substantial maintenance burden and that will benefit all users of the gem. We encourage anyone that needs additional or different behaviour to either create their own gem that builds off of discard or implement a new package with the different behaviour.
|
275
|
+
|
276
|
+
Discard is very simple and we like it that way. Creating your own clone or fork with slightly different behaviour may not be that much work!
|
277
|
+
|
278
|
+
If you find a bug in discard, please report it! We try to keep up with any issues and keep the gem running smoothly for everyone! You can report issues [here](https://github.com/jhawthorn/discard/issues).
|
189
279
|
|
190
280
|
## License
|
191
281
|
|
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", "< 8"
|
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
|
|
@@ -12,32 +19,151 @@ module Discard
|
|
12
19
|
scope :with_discarded, ->{ unscope(where: discard_column) }
|
13
20
|
|
14
21
|
define_model_callbacks :discard
|
22
|
+
define_model_callbacks :undiscard
|
15
23
|
end
|
16
24
|
|
17
|
-
|
25
|
+
# :nodoc:
|
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
|
18
42
|
def discard_all
|
19
|
-
|
43
|
+
kept.each(&:discard)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Discards the records by instantiating each
|
47
|
+
# record and calling its {#discard!} method.
|
48
|
+
# Each object's callbacks are executed.
|
49
|
+
# Returns the collection of objects that were discarded.
|
50
|
+
#
|
51
|
+
# Note: Instantiation, callback execution, and update of each
|
52
|
+
# record can be time consuming when you're discarding 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 discard many
|
55
|
+
# rows quickly, without concern for their associations or callbacks, use
|
56
|
+
# #update_all!(discarded_at: Time.current) instead.
|
57
|
+
#
|
58
|
+
# ==== Examples
|
59
|
+
#
|
60
|
+
# Person.where(age: 0..18).discard_all!
|
61
|
+
def discard_all!
|
62
|
+
kept.each(&:discard!)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Undiscards the records by instantiating each
|
66
|
+
# record and calling its {#undiscard} method.
|
67
|
+
# Each object's callbacks are executed.
|
68
|
+
# Returns the collection of objects that were undiscarded.
|
69
|
+
#
|
70
|
+
# Note: Instantiation, callback execution, and update of each
|
71
|
+
# record can be time consuming when you're undiscarding many records at
|
72
|
+
# once. It generates at least one SQL +UPDATE+ query per record (or
|
73
|
+
# possibly more, to enforce your callbacks). If you want to undiscard many
|
74
|
+
# rows quickly, without concern for their associations or callbacks, use
|
75
|
+
# #update_all(discarded_at: nil) instead.
|
76
|
+
#
|
77
|
+
# ==== Examples
|
78
|
+
#
|
79
|
+
# Person.where(age: 0..18).undiscard_all
|
80
|
+
def undiscard_all
|
81
|
+
discarded.each(&:undiscard)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Undiscards the records by instantiating each
|
85
|
+
# record and calling its {#undiscard!} method.
|
86
|
+
# Each object's callbacks are executed.
|
87
|
+
# Returns the collection of objects that were undiscarded.
|
88
|
+
#
|
89
|
+
# Note: Instantiation, callback execution, and update of each
|
90
|
+
# record can be time consuming when you're undiscarding many records at
|
91
|
+
# once. It generates at least one SQL +UPDATE+ query per record (or
|
92
|
+
# possibly more, to enforce your callbacks). If you want to undiscard many
|
93
|
+
# rows quickly, without concern for their associations or callbacks, use
|
94
|
+
# #update_all!(discarded_at: nil) instead.
|
95
|
+
#
|
96
|
+
# ==== Examples
|
97
|
+
#
|
98
|
+
# Person.where(age: 0..18).undiscard_all!
|
99
|
+
def undiscard_all!
|
100
|
+
discarded.each(&:undiscard!)
|
20
101
|
end
|
21
102
|
end
|
22
103
|
|
104
|
+
# @return [Boolean] true if this record has been discarded, otherwise false
|
23
105
|
def discarded?
|
24
|
-
|
106
|
+
self[self.class.discard_column].present?
|
25
107
|
end
|
26
108
|
|
109
|
+
# @return [Boolean] false if this record has been discarded, otherwise true
|
110
|
+
def undiscarded?
|
111
|
+
!discarded?
|
112
|
+
end
|
113
|
+
alias kept? undiscarded?
|
114
|
+
|
115
|
+
# Discard the record in the database
|
116
|
+
#
|
117
|
+
# @return [Boolean] true if successful, otherwise false
|
27
118
|
def discard
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self[self.class.discard_column] = Time.current
|
32
|
-
save
|
33
|
-
end
|
34
|
-
end
|
119
|
+
return false if discarded?
|
120
|
+
run_callbacks(:discard) do
|
121
|
+
update_attribute(self.class.discard_column, Time.current)
|
35
122
|
end
|
36
123
|
end
|
37
124
|
|
125
|
+
# Discard the record in the database
|
126
|
+
#
|
127
|
+
# There's a series of callbacks associated with #discard!. If the
|
128
|
+
# <tt>before_discard</tt> callback throws +:abort+ the action is cancelled
|
129
|
+
# and #discard! raises {Discard::RecordNotDiscarded}.
|
130
|
+
#
|
131
|
+
# @return [Boolean] true if successful
|
132
|
+
# @raise {Discard::RecordNotDiscarded}
|
133
|
+
def discard!
|
134
|
+
discard || _raise_record_not_discarded
|
135
|
+
end
|
136
|
+
|
137
|
+
# Undiscard the record in the database
|
138
|
+
#
|
139
|
+
# @return [Boolean] true if successful, otherwise false
|
38
140
|
def undiscard
|
39
|
-
|
40
|
-
|
141
|
+
return unless discarded?
|
142
|
+
run_callbacks(:undiscard) do
|
143
|
+
update_attribute(self.class.discard_column, nil)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Discard the record in the database
|
148
|
+
#
|
149
|
+
# There's a series of callbacks associated with #undiscard!. If the
|
150
|
+
# <tt>before_undiscard</tt> callback throws +:abort+ the action is cancelled
|
151
|
+
# and #undiscard! raises {Discard::RecordNotUndiscarded}.
|
152
|
+
#
|
153
|
+
# @return [Boolean] true if successful
|
154
|
+
# @raise {Discard::RecordNotUndiscarded}
|
155
|
+
def undiscard!
|
156
|
+
undiscard || _raise_record_not_undiscarded
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def _raise_record_not_discarded
|
162
|
+
raise ::Discard::RecordNotDiscarded.new("Failed to discard the record", self)
|
163
|
+
end
|
164
|
+
|
165
|
+
def _raise_record_not_undiscarded
|
166
|
+
raise ::Discard::RecordNotUndiscarded.new("Failed to undiscard the record", self)
|
41
167
|
end
|
42
168
|
end
|
43
169
|
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:
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-16 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: '8'
|
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: '8'
|
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
|
@@ -122,9 +122,14 @@ executables: []
|
|
122
122
|
extensions: []
|
123
123
|
extra_rdoc_files: []
|
124
124
|
files:
|
125
|
+
- ".github/ISSUE_TEMPLATE/bug-report.md"
|
126
|
+
- ".github/ISSUE_TEMPLATE/feature-proposal.md"
|
127
|
+
- ".github/workflows/test.yml"
|
125
128
|
- ".gitignore"
|
126
129
|
- ".rspec"
|
127
130
|
- ".travis.yml"
|
131
|
+
- ".yardopts"
|
132
|
+
- CHANGELOG.md
|
128
133
|
- CODE_OF_CONDUCT.md
|
129
134
|
- Gemfile
|
130
135
|
- LICENSE.txt
|
@@ -134,13 +139,14 @@ files:
|
|
134
139
|
- bin/setup
|
135
140
|
- discard.gemspec
|
136
141
|
- lib/discard.rb
|
142
|
+
- lib/discard/errors.rb
|
137
143
|
- lib/discard/model.rb
|
138
144
|
- lib/discard/version.rb
|
139
145
|
homepage: https://github.com/jhawthorn/discard
|
140
146
|
licenses:
|
141
147
|
- MIT
|
142
148
|
metadata: {}
|
143
|
-
post_install_message:
|
149
|
+
post_install_message:
|
144
150
|
rdoc_options: []
|
145
151
|
require_paths:
|
146
152
|
- lib
|
@@ -155,9 +161,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
161
|
- !ruby/object:Gem::Version
|
156
162
|
version: '0'
|
157
163
|
requirements: []
|
158
|
-
|
159
|
-
|
160
|
-
signing_key:
|
164
|
+
rubygems_version: 3.2.22
|
165
|
+
signing_key:
|
161
166
|
specification_version: 4
|
162
167
|
summary: ActiveRecord soft-deletes done right
|
163
168
|
test_files: []
|