acts_as_paranoid 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a57d617e3e2f52608fefad1c84bba5d6825d1b9ea96e681ade8739461ef35796
4
- data.tar.gz: 549ef0573150ba7f30740a6145cb6b52ef2916b3584199fc0bab8eba17271eb0
3
+ metadata.gz: b0a0cec714f9701fb054580a39b677e73a3cfd3fa1909afd29dfedbbfdcb82d0
4
+ data.tar.gz: bef6e1c18c141b3035d5a4b3c33cad22a398d06250f1f09730ae39662e48a0ca
5
5
  SHA512:
6
- metadata.gz: 13ac9918fc0dc45e93d02ad799f20fe9c13afd4eba4a0b81c52201bc495a690dc80b1ab9ba9e700b7d072a61c5973dcf8d134a1049c581d61c1c0120f4444d59
7
- data.tar.gz: 6bba5d24a72d45b5de90670e1c577d6172160d1ee5d3850a3d9dd3845eb2fd73060a6d441d738b99dc9aea516189a6807c0406f377997dddf8ac443f88db6fbd
6
+ metadata.gz: 804904753df983b7e366d4a6af8f4c80b24cda018a2cd2534721f7d943cc23556a9a1a9c77a7f37de335c60a3fde99639036315f235b303a60fdefc871e693b9
7
+ data.tar.gz: 40e9a9229af5706aa62e319e49c74b60b2d89e8880d46f6106ec51ef44e1e4811715b514b5e691500abc65805dd521c6741130f8eba0b892d2738cfe8efca23a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  Notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.8.0
6
+
7
+ * Do not set `paranoid_value` when destroying fully ([#238], by [Aymeric Le Dorze][aymeric-ledorze])
8
+ * Make helper methods for dependent associations private ([#239], by [Matijs van Zuijlen][mvz])
9
+ * Raise ActiveRecord::RecordNotDestroyed if destroy returns false ([#240], by [Hao Liu][leomayleomay])
10
+ * Make unscoping by `with_deleted` less blunt ([#241], by [Matijs van Zuijlen][mvz])
11
+ * Drop support for Ruby 2.4 and 2.5 ([#243] and [#245] by [Matijs van Zuijlen][mvz])
12
+ * Remove deprecated methods ([#244] by [Matijs van Zuijlen][mvz])
13
+ * Remove test files from the gem ([#261] by [Matijs van Zuijlen][mvz])
14
+ * Add support for Rails 7 ([#262] by [Vederis Leunardus][cloudsbird])
15
+
16
+ ## 0.7.3
17
+
18
+ ## Improvements
19
+
20
+ * Fix deletion time scopes ([#212] by [Matijs van Zuijlen][mvz])
21
+ * Reload `has_one` associations after dependent recovery ([#214],
22
+ by [Matijs van Zuijlen][mvz])
23
+ * Make dependent recovery work when parent is non-optional ([#227],
24
+ by [Matijs van Zuijlen][mvz])
25
+ * Avoid querying nil `belongs_to` associations when recovering ([#219],
26
+ by [Matijs van Zuijlen][mvz])
27
+ * On relations, deprecate `destroy!` in favour of `destroy_fully!` ([#222],
28
+ by [Matijs van Zuijlen][mvz])
29
+ * Deprecate the undocumented `:recovery_value` setting. Calculate the correct
30
+ value instead. ([#220], by [Matijs van Zuijlen][mvz])
31
+
32
+ ## Developer experience
33
+
34
+ * Log ActiveRecord activity to a visible log during tests ([#218],
35
+ by [Matijs van Zuijlen][mvz])
36
+
37
+ ## 0.7.2
38
+
39
+ * Do not set boolean column to NULL on recovery if nulls are not allowed
40
+ ([#193], by [Shodai Suzuki][soartec-lab])
41
+ * Add a CONTRIBUTING.md file ([#207], by [Matijs van Zuijlen][mvz])
42
+
43
+ ## 0.7.1
44
+
45
+ * Support Rails 6.1 ([#191], by [Matijs van Zuijlen][mvz])
46
+ * Support `belongs_to` with both `:touch` and `:counter_cache` options ([#208],
47
+ by [Matijs van Zuijlen][mvz] with [Paul Druziak][pauldruziak])
48
+ * Support Ruby 3.0 ([#209], by [Matijs van Zuijlen][mvz])
49
+
5
50
  ## 0.7.0
6
51
 
7
52
  ### Breaking changes
@@ -70,21 +115,46 @@ Notable changes to this project will be documented in this file.
70
115
  [AlexWheeler]: https://github.com/AlexWheeler
71
116
  [RomainAlexandre]: https://github.com/RomainAlexandre
72
117
  [avinoth]: https://github.com/avinoth
118
+ [cloudsbird]: https://github.com/cloudsbird
73
119
  [aymeric-ledorze]: https://github.com/aymeric-ledorze
74
120
  [danielricecodes]: https://github.com/danielricecodes
75
121
  [jbryant92]: https://github.com/jbryant92
76
122
  [kevinmcalear]: https://github.com/kevinmcalear
123
+ [leomayleomay]: https://github.com/leomayleomay
77
124
  [marycodes2]: https://github.com/marycodes2
78
125
  [mvz]: https://github.com/mvz
79
126
  [nedcampion]: https://github.com/nedcampion
80
127
  [ri4a]: https://github.com/ri4a
128
+ [pauldruziak]: https://github.com/pauldruziak
81
129
  [shadydealer]: https://github.com/shadydealer
130
+ [soartec-lab]: https://github.com/soartec-lab
82
131
  [thebravoman]: https://github.com/thebravoman
83
132
  [valeriecodes]: https://github.com/valeriecodes
84
133
  [wtfspm]: https://github.com/wtfspm
85
134
 
86
135
  <!-- issues & pull requests -->
87
136
 
137
+ [#262]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/262
138
+ [#261]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/261
139
+ [#245]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/245
140
+ [#244]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/244
141
+ [#243]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/243
142
+ [#241]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/241
143
+ [#240]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/240
144
+ [#239]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/239
145
+ [#238]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/238
146
+ [#227]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/227
147
+ [#222]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/222
148
+ [#220]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/220
149
+ [#219]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/219
150
+ [#218]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/218
151
+ [#214]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/214
152
+ [#212]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/212
153
+ [#209]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/209
154
+ [#208]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/208
155
+ [#207]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/207
156
+ [#193]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/193
157
+ [#191]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/191
88
158
  [#175]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/175
89
159
  [#173]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/173
90
160
  [#171]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/171
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,58 @@
1
+ # Contributing to ActsAsParanoid
2
+
3
+ We welcome all contributions to ActsAsParanoid. Below are some guidelines to
4
+ help the process of handling issues and pull requests go smoothly.
5
+
6
+ ## Issues
7
+
8
+ When creating an issue, please provide as much information as possible, and
9
+ follow the guidelines below to make it easier for us to figure out what's going
10
+ on. If you miss any of these points we will probably ask you to improve the
11
+ ticket.
12
+
13
+ - Include a clear title describing the problem
14
+ - Describe what you are trying to achieve
15
+ - Describe what you did, preferably including relevant code
16
+ - Describe what you expected to happen
17
+ - Describe what happened instead, possibly including relevant output
18
+ - Use [code blocks](https://github.github.com/gfm/#fenced-code-blocks) to
19
+ format any code and output in your ticket to make it readable.
20
+
21
+ ## Pull requests
22
+
23
+ If you have an idea for a particular feature, it's probably best to create a
24
+ GitHub issue for it before trying to implement it yourself. That way, we can
25
+ discuss the feature and whether it makes sense to include in ActsAsParanoid itself
26
+ before putting in the work to implement it.
27
+
28
+ To send pull requests or patches, please follow the instructions below.
29
+ **If you get stuck, please make a pull request anyway and we'll try to
30
+ help out.**
31
+
32
+ - Make sure `rake test` runs without reporting any failures.
33
+ - Add tests for your feature. Otherwise, we can't see if it works or if we
34
+ break it later.
35
+ - Create a separate branch for your feature based off of latest master.
36
+ - Do not include changes that are irrelevant to your feature in the same
37
+ commit.
38
+ - Keep an eye on the build results in GitHub Actions. If the build fails and it
39
+ seems due to your changes, please update your pull request with a fix.
40
+
41
+ ### The review process
42
+
43
+ - We will try to review your pull request as soon as possible but we can make no
44
+ guarantees. Feel free to ping us now and again.
45
+ - We will probably ask you to rebase your branch on current master at some point
46
+ during the review process.
47
+ If you are unsure how to do this,
48
+ [this in-depth guide](https://git-rebase.io/) should help out.
49
+ - If you have any unclear commit messages, work-in-progress commits, or commits
50
+ that just fix a mistake in a previous commits, we will ask you to clean up
51
+ the history.
52
+ Again, [the git-rebase guide](https://git-rebase.io/) should help out.
53
+ - At the end of the review process we may still choose not to merge your pull
54
+ request. For example, this could happen if we decide the proposed feature
55
+ should not be part of ActsAsParanoid, or if the technical implementation does not
56
+ match where we want to go with the architecture the project.
57
+ - We will generally not merge any pull requests that make the build fail, unless
58
+ it's very clearly not related to the changes in the pull request.
data/README.md CHANGED
@@ -9,9 +9,9 @@ recoverable later.
9
9
 
10
10
  ## Support
11
11
 
12
- **This branch targets Rails 5.2+ and Ruby 2.4+ only**
12
+ **This branch targets Rails 5.2+ and Ruby 2.5+ only**
13
13
 
14
- If you're working with Rails 5.1 and earlier, or with Ruby 2.3 or earlier,
14
+ If you're working with Rails 5.1 and earlier, or with Ruby 2.4 or earlier,
15
15
  please switch to the corresponding branch or require an older version of the
16
16
  `acts_as_paranoid` gem.
17
17
 
@@ -27,7 +27,7 @@ please switch to the corresponding branch or require an older version of the
27
27
  #### Install gem:
28
28
 
29
29
  ```ruby
30
- gem 'acts_as_paranoid', '~> 0.7.0'
30
+ gem 'acts_as_paranoid'
31
31
  ```
32
32
 
33
33
  ```shell
@@ -255,9 +255,9 @@ Paranoiac.create(name: 'foo').destroy
255
255
  Paranoiac.with_deleted.first.deleted? #=> true
256
256
  ```
257
257
 
258
- After the first call to .destroy the object is deleted?
258
+ After the first call to `.destroy` the object is `deleted?`.
259
259
 
260
- You can check if the object is fully destroyed with destroyed_fully? or deleted_fully?.
260
+ You can check if the object is fully destroyed with `destroyed_fully?` or `deleted_fully?`.
261
261
 
262
262
  ```ruby
263
263
  Paranoiac.create(name: 'foo').destroy
@@ -88,6 +88,14 @@ module ActsAsParanoid
88
88
  end
89
89
  end
90
90
 
91
+ def recovery_value
92
+ if boolean_type_not_nullable?
93
+ false
94
+ else
95
+ nil
96
+ end
97
+ end
98
+
91
99
  protected
92
100
 
93
101
  def define_deleted_time_scopes
@@ -96,19 +104,25 @@ module ActsAsParanoid
96
104
  }
97
105
 
98
106
  scope :deleted_after_time, lambda { |time|
99
- where("#{table_name}.#{paranoid_column} > ?", time)
107
+ only_deleted
108
+ .where("#{table_name}.#{paranoid_column} > ?", time)
100
109
  }
101
110
  scope :deleted_before_time, lambda { |time|
102
- where("#{table_name}.#{paranoid_column} < ?", time)
111
+ only_deleted
112
+ .where("#{table_name}.#{paranoid_column} < ?", time)
103
113
  }
104
114
  end
105
115
 
106
116
  def without_paranoid_default_scope
107
117
  scope = all
108
118
 
119
+ # unscope avoids applying the default scope when using this scope for associations
109
120
  scope = scope.unscope(where: paranoid_column)
110
- # Fix problems with unscope group chain
111
- scope = scope.unscoped if scope.to_sql.include? paranoid_default_scope.to_sql
121
+
122
+ paranoid_where_clause =
123
+ ActiveRecord::Relation::WhereClause.new([paranoid_default_scope])
124
+
125
+ scope.where_clause = all.where_clause - paranoid_where_clause
112
126
 
113
127
  scope
114
128
  end
@@ -138,11 +152,10 @@ module ActsAsParanoid
138
152
  # Handle composite keys, otherwise we would just use
139
153
  # `self.class.primary_key.to_sym => self.id`.
140
154
  self.class
141
- .delete_all!(Hash[[Array(self.class.primary_key), Array(id)].transpose])
155
+ .delete_all!([Array(self.class.primary_key), Array(id)].transpose.to_h)
142
156
  decrement_counters_on_associations
143
157
  end
144
158
 
145
- stale_paranoid_value
146
159
  @destroyed = true
147
160
  freeze
148
161
  end
@@ -150,6 +163,12 @@ module ActsAsParanoid
150
163
  end
151
164
 
152
165
  def destroy!
166
+ destroy || raise(
167
+ ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record", self)
168
+ )
169
+ end
170
+
171
+ def destroy
153
172
  if !deleted?
154
173
  with_transaction_returning_status do
155
174
  run_callbacks :destroy do
@@ -157,7 +176,7 @@ module ActsAsParanoid
157
176
  # Handle composite keys, otherwise we would just use
158
177
  # `self.class.primary_key.to_sym => self.id`.
159
178
  self.class
160
- .delete_all(Hash[[Array(self.class.primary_key), Array(id)].transpose])
179
+ .delete_all([Array(self.class.primary_key), Array(id)].transpose.to_h)
161
180
  decrement_counters_on_associations
162
181
  end
163
182
 
@@ -172,8 +191,6 @@ module ActsAsParanoid
172
191
  end
173
192
  end
174
193
 
175
- alias destroy destroy!
176
-
177
194
  def recover(options = {})
178
195
  return if !deleted?
179
196
 
@@ -185,16 +202,16 @@ module ActsAsParanoid
185
202
 
186
203
  self.class.transaction do
187
204
  run_callbacks :recover do
188
- if options[:recursive]
189
- recover_dependent_associations(options[:recovery_window], options)
190
- end
191
205
  increment_counters_on_associations
192
- self.paranoid_value = self.class.paranoid_configuration[:recovery_value]
193
- if options[:raise_error]
194
- save!
195
- else
196
- save
197
- end
206
+ deleted_value = paranoid_value
207
+ self.paranoid_value = self.class.recovery_value
208
+ result = if options[:raise_error]
209
+ save!
210
+ else
211
+ save
212
+ end
213
+ recover_dependent_associations(deleted_value, options) if options[:recursive]
214
+ result
198
215
  end
199
216
  end
200
217
  end
@@ -205,34 +222,6 @@ module ActsAsParanoid
205
222
  recover(options)
206
223
  end
207
224
 
208
- def recover_dependent_associations(window, options)
209
- self.class.dependent_associations.each do |reflection|
210
- next unless (klass = get_reflection_class(reflection)).paranoid?
211
-
212
- scope = klass.only_deleted.merge(get_association_scope(reflection: reflection))
213
-
214
- # We can only recover by window if both parent and dependant have a
215
- # paranoid column type of :time.
216
- if self.class.paranoid_column_type == :time && klass.paranoid_column_type == :time
217
- scope = scope.deleted_inside_time_window(paranoid_value, window)
218
- end
219
-
220
- scope.each do |object|
221
- object.recover(options)
222
- end
223
- end
224
- end
225
-
226
- def destroy_dependent_associations!
227
- self.class.dependent_associations.each do |reflection|
228
- next unless (klass = get_reflection_class(reflection)).paranoid?
229
-
230
- klass
231
- .only_deleted.merge(get_association_scope(reflection: reflection))
232
- .each(&:destroy!)
233
- end
234
- end
235
-
236
225
  def deleted?
237
226
  return true if @destroyed
238
227
 
@@ -255,32 +244,68 @@ module ActsAsParanoid
255
244
 
256
245
  private
257
246
 
258
- def get_association_scope(reflection:)
259
- ActiveRecord::Associations::AssociationScope.scope(association(reflection.name))
247
+ def recover_dependent_associations(deleted_value, options)
248
+ self.class.dependent_associations.each do |reflection|
249
+ recover_dependent_association(reflection, deleted_value, options)
250
+ end
260
251
  end
261
252
 
262
- def get_reflection_class(reflection)
263
- if reflection.macro == :belongs_to && reflection.options.include?(:polymorphic)
264
- send(reflection.foreign_type).constantize
265
- else
266
- reflection.klass
253
+ def destroy_dependent_associations!
254
+ self.class.dependent_associations.each do |reflection|
255
+ assoc = association(reflection.name)
256
+ next unless (klass = assoc.klass).paranoid?
257
+
258
+ klass
259
+ .only_deleted.merge(get_association_scope(assoc))
260
+ .each(&:destroy!)
267
261
  end
268
262
  end
269
263
 
264
+ def recover_dependent_association(reflection, deleted_value, options)
265
+ assoc = association(reflection.name)
266
+ return unless (klass = assoc.klass).paranoid?
267
+
268
+ if reflection.belongs_to? && attributes[reflection.association_foreign_key].nil?
269
+ return
270
+ end
271
+
272
+ scope = klass.only_deleted.merge(get_association_scope(assoc))
273
+
274
+ # We can only recover by window if both parent and dependant have a
275
+ # paranoid column type of :time.
276
+ if self.class.paranoid_column_type == :time && klass.paranoid_column_type == :time
277
+ scope = scope.deleted_inside_time_window(deleted_value, options[:recovery_window])
278
+ end
279
+
280
+ recovered = false
281
+ scope.each do |object|
282
+ object.recover(options)
283
+ recovered = true
284
+ end
285
+
286
+ assoc.reload if recovered && reflection.has_one? && assoc.loaded?
287
+ end
288
+
289
+ def get_association_scope(dependent_association)
290
+ ActiveRecord::Associations::AssociationScope.scope(dependent_association)
291
+ end
292
+
270
293
  def paranoid_value=(value)
271
294
  write_attribute(self.class.paranoid_column, value)
272
295
  end
273
296
 
274
297
  def update_counters_on_associations(method_sym)
275
- return unless [:decrement_counter, :increment_counter].include? method_sym
276
-
277
298
  each_counter_cached_association_reflection do |assoc_reflection|
299
+ reflection_options = assoc_reflection.options
300
+ next unless reflection_options[:counter_cache]
301
+
278
302
  associated_object = send(assoc_reflection.name)
279
303
  next unless associated_object
280
304
 
281
305
  counter_cache_column = assoc_reflection.counter_cache_column
282
306
  associated_object.class.send(method_sym, counter_cache_column,
283
307
  associated_object.id)
308
+ associated_object.touch if reflection_options[:touch]
284
309
  end
285
310
  end
286
311
 
@@ -29,7 +29,7 @@ module ActsAsParanoid
29
29
  end
30
30
  end
31
31
 
32
- def destroy!(id_or_array)
32
+ def destroy_fully!(id_or_array)
33
33
  where(primary_key => id_or_array).orig_delete_all
34
34
  end
35
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActsAsParanoid
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -27,18 +27,18 @@ module ActsAsParanoid
27
27
  column_type: "time",
28
28
  recover_dependent_associations: true,
29
29
  dependent_recovery_window: 2.minutes,
30
- recovery_value: nil,
31
30
  double_tap_destroys_fully: true
32
31
  }
33
32
  if options[:column_type] == "string"
34
33
  paranoid_configuration.merge!(deleted_value: "deleted")
35
34
  end
36
- paranoid_configuration.merge!(allow_nulls: true) if options[:column_type] == "boolean"
35
+
37
36
  paranoid_configuration.merge!(options) # user options
38
37
 
39
38
  unless %w[time boolean string].include? paranoid_configuration[:column_type]
40
- raise ArgumentError, "'time', 'boolean' or 'string' expected" \
41
- " for :column_type option, got #{paranoid_configuration[:column_type]}"
39
+ raise ArgumentError,
40
+ "'time', 'boolean' or 'string' expected for :column_type option," \
41
+ " got #{paranoid_configuration[:column_type]}"
42
42
  end
43
43
 
44
44
  return if paranoid?