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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e3d4174c8881bc3bf1fa453aafb3d966d8361292
4
- data.tar.gz: 858b1edbe5d9c09144239a13cc3ca662f1485547
2
+ SHA256:
3
+ metadata.gz: 27523357b203abf0f0928633448c778ec25ca89f36a9ec2e8178149b9a37ab67
4
+ data.tar.gz: 0650603e1aa80be6727824df9b74486fa8ad7af81f44074bf4c6db7b5b7c88cd
5
5
  SHA512:
6
- metadata.gz: 1246d6187b35af15ceb8d5e8a1b914e09a45ba0380d833ca638305336d3075e67f60edc67fad55c49e994b0ab9e9f60b3d9a916acc837d3416b3b89ec56e2bcd
7
- data.tar.gz: 1d098bf07dcefdd3d338b0a4a73b206dfd96e5c39b8abbabb17cd749970ff9c54efd3d35fe7f6f363f8c35b076b7e54f278f1f3cbb43de7330da0a363671f2f5
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.7
5
- - 2.3.4
6
- - 2.4.1
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.x'
12
- before_install: gem install bundler -v 1.14.6
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
@@ -0,0 +1,8 @@
1
+ --protected
2
+ --no-private
3
+ --embed-mixin ClassMethods
4
+ -
5
+ README.md
6
+ CHANGELOG.md
7
+ CODE_OF_CONDUCT.md
8
+ LICENSE.txt
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
@@ -3,5 +3,9 @@ source 'https://rubygems.org'
3
3
  rails_version = ENV['RAILS_VERSION']
4
4
  gem 'activerecord', rails_version
5
5
 
6
+ if sqlite_version = ENV['SQLITE_VERSION']
7
+ gem 'sqlite3', sqlite_version
8
+ end
9
+
6
10
  # Specify your gem's dependencies in discard.gemspec
7
11
  gemspec
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 up
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
- **Discard a record**
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
- @post = Post.first # => #<Post id: 1, ...>
82
- @post.discard!
83
- @post.discarded? # => true
84
- @post.discarded_at # => 2017-04-18 18:49:49 -0700
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
- **From a controller**
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
- **Working with associations**
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
- has_many :posts
127
+ belongs_to :post
129
128
 
130
129
  include Discard::Model
131
- scope :kept, -> { undiscarded.joins(:posts).merge(Post.kept) }
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
- **Default scope**
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
- **Custom column**
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/discard.
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 "bundler/gem_tasks"
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
2
4
 
3
- begin
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
- task :default => :spec
7
+ desc 'Run the test suite'
8
+ task default: :rspec
data/discard.gemspec CHANGED
@@ -1,4 +1,5 @@
1
- # coding: utf-8
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", '>= 4.2', '< 6'
25
- spec.add_development_dependency "bundler", "~> 1.14"
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
- class_methods do
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
- all.each(&:discard)
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
- !!self[self.class.discard_column]
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
- unless discarded?
29
- with_transaction_returning_status do
30
- run_callbacks(:discard) do
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
- self[self.class.discard_column] = nil
40
- save
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
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Discard
2
- VERSION = "0.2.0"
4
+ # Discard version
5
+ VERSION = "1.2.1".freeze
3
6
  end
data/lib/discard.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record"
2
4
 
3
5
  require "discard/version"
6
+ require "discard/errors"
4
7
  require "discard/model"
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: 0.2.0
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: 2017-11-22 00:00:00.000000000 Z
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: '6'
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: '6'
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: '1.14'
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: '1.14'
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
- rubyforge_project:
159
- rubygems_version: 2.6.11
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: []