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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99f47416f1c4bdc6ed76c7814e6f1b88baea019acf8d38abbbbf709d42db0efa
4
- data.tar.gz: c6699d73d311300e8cdeecd1cb2498c3b25197a10292f28993abc237ad1abd5f
3
+ metadata.gz: 2418547a70d311cc7d970ce492f505a0807db23a03eebe2ae1e1f7589a0edb8b
4
+ data.tar.gz: a45947c018416ee63a873fc0f94f143f20a6a5e34892420893bf3fe50b9a5406
5
5
  SHA512:
6
- metadata.gz: ebd11402b212e931423a98484b598675eac16ef4eb878a701fd1479da488bd503058d0679053be16b43bab4e651b50cd512c6d14f19d131dae02fbcbc3396c78
7
- data.tar.gz: f5baab64f5a951f1c55c73615490587d44291296645c4e4317c6918dcdc52675c3fa1856f86db03de2341518d9836ab2ad7e011d914599ebbc27d425406f5ccf
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.9
5
- - 2.3.6
6
- - 2.4.3
7
- - 2.5.0
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
- before_install: gem install bundler -v 1.14.6
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
@@ -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,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
@@ -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
@@ -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 up
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
- **Discard a record**
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
- **From a controller**
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
- **Undiscard a record**
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
- **Working with associations**
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
- has_many :posts
122
+ belongs_to :post
118
123
 
119
124
  include Discard::Model
120
- scope :kept, -> { undiscarded.joins(:posts).merge(Post.kept) }
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
- **Default scope**
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
- **Custom column**
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
- **Callbacks**
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
- **Working with Devise**
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 && !discarded_at
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 "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", "< 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
- all.each(&:discard)
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
- all.each(&:undiscard)
62
+ discarded.each(&:undiscard)
24
63
  end
25
64
  end
26
65
 
27
- # @return [true,false] true if this record has been discarded, otherwise false
66
+ # @return [Boolean] true if this record has been discarded, otherwise false
28
67
  def discarded?
29
- !!self[self.class.discard_column]
68
+ self[self.class.discard_column].present?
30
69
  end
31
70
 
32
- # @return [true,false] true if successful, otherwise false
71
+ # Discard the record in the database
72
+ #
73
+ # @return [Boolean] true if successful, otherwise false
33
74
  def discard
34
- unless discarded?
35
- with_transaction_returning_status do
36
- run_callbacks(:discard) do
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
- # @return [true,false] true if successful, otherwise false
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
- if discarded?
47
- with_transaction_returning_status do
48
- run_callbacks(:undiscard) do
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
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Discard
2
- VERSION = "1.0.0"
4
+ # Discard version
5
+ VERSION = "1.1.0".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: 1.0.0
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: 2018-03-16 00:00:00.000000000 Z
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: '6'
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: '6'
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: '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
@@ -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
- rubyforge_project:
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