acts_as_paranoid 0.7.2 → 0.8.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
2
  SHA256:
3
- metadata.gz: 73cbdef60cb81f9506a3763c13b0380f83169b8c68f0b914305d828b90202d25
4
- data.tar.gz: 2023f4db3125ff3db3dd9dc7b0fb0142e92d6f8f54bc433557d5dd0dc40fb853
3
+ metadata.gz: be004bb0de25cdda73697fa22c07d0a64e1d98c071506d4b0a608a3d3231e103
4
+ data.tar.gz: 9334fe5730d07f94d087f3e6f1d4803126224a4106cc378d9f6263fc15295d93
5
5
  SHA512:
6
- metadata.gz: 1c7b2ff7d8655621915c11630012ff946a196b46fd0a175facad75d90aa4f6ecae1ae6df1e627f9cf778faa46d26fb559b275e62101a4ba402007d545014f43d
7
- data.tar.gz: ec47e5232127609e3e83b9bf22b142a821e955b55a77e082f21ceaeb5e62c390253fee6815cfc3a4a795257eb68c15f29eb5adbad8e511703718e4278d84e52c
6
+ metadata.gz: c3e7ebd79d467118c9c91374088f3fe3a203ae6ca914fa036646fb77e7f4c1c9ba4e1c653dc2311ac1bd53bbf2517cb1a00ee06b32e0f09d6937bfb1ea8f6247
7
+ data.tar.gz: 99c7d3d41d9a94b26d2fed6ce5b01ea2fddfbc09bd958d9ca990a7b2d92c466a876e23011db5f7fda02b4efb9cd417b9a8dfe62a432757e788e7a89c898dbcd0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  Notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.8.1
6
+
7
+ * Officially support Ruby 3.1 ([#268], by [Matijs van Zuijlen][mvz])
8
+ * Fix association building for `belongs_to` with `:with_deleted` option ([#277], by [Matijs van Zuijlen][mvz])
9
+
10
+ ## 0.8.0
11
+
12
+ * Do not set `paranoid_value` when destroying fully ([#238], by [Aymeric Le Dorze][aymeric-ledorze])
13
+ * Make helper methods for dependent associations private ([#239], by [Matijs van Zuijlen][mvz])
14
+ * Raise ActiveRecord::RecordNotDestroyed if destroy returns false ([#240], by [Hao Liu][leomayleomay])
15
+ * Make unscoping by `with_deleted` less blunt ([#241], by [Matijs van Zuijlen][mvz])
16
+ * Drop support for Ruby 2.4 and 2.5 ([#243] and [#245] by [Matijs van Zuijlen][mvz])
17
+ * Remove deprecated methods ([#244] by [Matijs van Zuijlen][mvz])
18
+ * Remove test files from the gem ([#261] by [Matijs van Zuijlen][mvz])
19
+ * Add support for Rails 7 ([#262] by [Vederis Leunardus][cloudsbird])
20
+
21
+ ## 0.7.3
22
+
23
+ ## Improvements
24
+
25
+ * Fix deletion time scopes ([#212] by [Matijs van Zuijlen][mvz])
26
+ * Reload `has_one` associations after dependent recovery ([#214],
27
+ by [Matijs van Zuijlen][mvz])
28
+ * Make dependent recovery work when parent is non-optional ([#227],
29
+ by [Matijs van Zuijlen][mvz])
30
+ * Avoid querying nil `belongs_to` associations when recovering ([#219],
31
+ by [Matijs van Zuijlen][mvz])
32
+ * On relations, deprecate `destroy!` in favour of `destroy_fully!` ([#222],
33
+ by [Matijs van Zuijlen][mvz])
34
+ * Deprecate the undocumented `:recovery_value` setting. Calculate the correct
35
+ value instead. ([#220], by [Matijs van Zuijlen][mvz])
36
+
37
+ ## Developer experience
38
+
39
+ * Log ActiveRecord activity to a visible log during tests ([#218],
40
+ by [Matijs van Zuijlen][mvz])
41
+
5
42
  ## 0.7.2
6
43
 
7
44
  * Do not set boolean column to NULL on recovery if nulls are not allowed
@@ -83,10 +120,12 @@ Notable changes to this project will be documented in this file.
83
120
  [AlexWheeler]: https://github.com/AlexWheeler
84
121
  [RomainAlexandre]: https://github.com/RomainAlexandre
85
122
  [avinoth]: https://github.com/avinoth
123
+ [cloudsbird]: https://github.com/cloudsbird
86
124
  [aymeric-ledorze]: https://github.com/aymeric-ledorze
87
125
  [danielricecodes]: https://github.com/danielricecodes
88
126
  [jbryant92]: https://github.com/jbryant92
89
127
  [kevinmcalear]: https://github.com/kevinmcalear
128
+ [leomayleomay]: https://github.com/leomayleomay
90
129
  [marycodes2]: https://github.com/marycodes2
91
130
  [mvz]: https://github.com/mvz
92
131
  [nedcampion]: https://github.com/nedcampion
@@ -100,6 +139,24 @@ Notable changes to this project will be documented in this file.
100
139
 
101
140
  <!-- issues & pull requests -->
102
141
 
142
+ [#277]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/277
143
+ [#268]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/268
144
+ [#262]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/262
145
+ [#261]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/261
146
+ [#245]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/245
147
+ [#244]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/244
148
+ [#243]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/243
149
+ [#241]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/241
150
+ [#240]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/240
151
+ [#239]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/239
152
+ [#238]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/238
153
+ [#227]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/227
154
+ [#222]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/222
155
+ [#220]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/220
156
+ [#219]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/219
157
+ [#218]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/218
158
+ [#214]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/214
159
+ [#212]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/212
103
160
  [#209]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/209
104
161
  [#208]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/208
105
162
  [#207]: https://github.com/ActsAsParanoid/acts_as_paranoid/pull/207
data/CONTRIBUTING.md CHANGED
@@ -5,10 +5,10 @@ help the process of handling issues and pull requests go smoothly.
5
5
 
6
6
  ## Issues
7
7
 
8
- When creating an issue, please try to provide as much information as possible.
9
- Also, please follow the guidelines below to make it easier for us to figure out
10
- what's going on. If you miss any of these points we will probably ask you to
11
- improve the ticket.
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
12
 
13
13
  - Include a clear title describing the problem
14
14
  - Describe what you are trying to achieve
@@ -25,20 +25,31 @@ GitHub issue for it before trying to implement it yourself. That way, we can
25
25
  discuss the feature and whether it makes sense to include in ActsAsParanoid itself
26
26
  before putting in the work to implement it.
27
27
 
28
- If you want to send pull requests or patches, try to follow the instructions
29
- below. **If you get stuck, please make a pull request anyway and we'll try to
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
30
  help out.**
31
31
 
32
- - Make sure `rake test` runs without reporting any failures.
32
+ - Make sure `bundle exec rake` runs without reporting any failures.
33
33
  - Add tests for your feature. Otherwise, we can't see if it works or if we
34
34
  break it later.
35
- - Make sure latest master merges cleanly with your branch. Things might
36
- have moved around since you forked.
37
- - Try not to include changes that are irrelevant to your feature in the
38
- same commit.
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.
39
38
  - Keep an eye on the build results in GitHub Actions. If the build fails and it
40
39
  seems due to your changes, please update your pull request with a fix.
41
40
 
41
+ ### Testing your changes
42
+
43
+ You can run the test suite with the latest version of all dependencies by running the following:
44
+
45
+ - Run `bundle install` if you haven't done so already, or `bundle update` to update the dependencies
46
+ - Run `bundle exec rake` to run the tests
47
+
48
+ To run the tests suite for a particular version of ActiveRecord use
49
+ [appraisal](https://github.com/thoughtbot/appraisal). For example, to run the
50
+ specs with ActiveRecord 6.1, run `appraisal active_record_61 rake`. See appraisal's
51
+ documentation for details.
52
+
42
53
  ### The review process
43
54
 
44
55
  - We will try to review your pull request as soon as possible but we can make no
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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsParanoid
4
+ # Override for ActiveRecord::Reflection::AssociationReflection
5
+ #
6
+ # This makes automatic finding of inverse associations work where the
7
+ # inverse is a belongs_to association with the :with_deleted option set.
8
+ #
9
+ # Specifying :with_deleted for the belongs_to association would stop the
10
+ # inverse from being calculated because it sets scope where there was none,
11
+ # and normally an association having a scope means ActiveRecord will not
12
+ # automatically find the inverse association.
13
+ #
14
+ # This override adds an exception to that rule only for the case where the
15
+ # scope was added just to support the :with_deleted option.
16
+ module AssociationReflection
17
+ if ActiveRecord::VERSION::MAJOR < 7
18
+ def can_find_inverse_of_automatically?(reflection)
19
+ options = reflection.options
20
+
21
+ if reflection.macro == :belongs_to && options[:with_deleted]
22
+ return false if options[:inverse_of] == false
23
+ return false if options[:foreign_key]
24
+
25
+ !options.fetch(:original_scope)
26
+ else
27
+ super
28
+ end
29
+ end
30
+ else
31
+ def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
32
+ if reflection.scope
33
+ options = reflection.options
34
+ return true if options[:with_deleted] && !options.fetch(:original_scope)
35
+ end
36
+
37
+ super
38
+ end
39
+ end
40
+ end
41
+ end
@@ -19,32 +19,45 @@ module ActsAsParanoid
19
19
 
20
20
  with_deleted = options.delete(:with_deleted)
21
21
  if with_deleted
22
- if scope
23
- old_scope = scope
24
- scope = proc do |*args|
25
- if old_scope.arity == 0
26
- instance_exec(&old_scope).with_deleted
27
- else
28
- old_scope.call(*args).with_deleted
29
- end
30
- end
31
- else
32
- scope = proc do
33
- if respond_to? :with_deleted
34
- self.with_deleted
35
- else
36
- all
37
- end
38
- end
39
- end
22
+ original_scope = scope
23
+ scope = make_scope_with_deleted(scope)
40
24
  end
41
25
 
42
26
  result = belongs_to_without_deleted(target, scope, **options)
43
27
 
44
- result.values.last.options[:with_deleted] = with_deleted if with_deleted
28
+ if with_deleted
29
+ options = result.values.last.options
30
+ options[:with_deleted] = with_deleted
31
+ options[:original_scope] = original_scope
32
+ end
45
33
 
46
34
  result
47
35
  end
36
+
37
+ private
38
+
39
+ def make_scope_with_deleted(scope)
40
+ if scope
41
+ old_scope = scope
42
+ scope = proc do |*args|
43
+ if old_scope.arity == 0
44
+ instance_exec(&old_scope).with_deleted
45
+ else
46
+ old_scope.call(*args).with_deleted
47
+ end
48
+ end
49
+ else
50
+ scope = proc do
51
+ if respond_to? :with_deleted
52
+ with_deleted
53
+ else
54
+ all
55
+ end
56
+ end
57
+ end
58
+
59
+ scope
60
+ end
48
61
  end
49
62
  end
50
63
  end
@@ -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
@@ -142,7 +156,6 @@ module ActsAsParanoid
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
@@ -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,18 +244,52 @@ 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
@@ -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.2"
4
+ VERSION = "0.8.1"
5
5
  end
@@ -5,6 +5,7 @@ require "acts_as_paranoid/core"
5
5
  require "acts_as_paranoid/associations"
6
6
  require "acts_as_paranoid/validations"
7
7
  require "acts_as_paranoid/relation"
8
+ require "acts_as_paranoid/association_reflection"
8
9
 
9
10
  module ActsAsParanoid
10
11
  def paranoid?
@@ -27,22 +28,18 @@ module ActsAsParanoid
27
28
  column_type: "time",
28
29
  recover_dependent_associations: true,
29
30
  dependent_recovery_window: 2.minutes,
30
- recovery_value: nil,
31
31
  double_tap_destroys_fully: true
32
32
  }
33
33
  if options[:column_type] == "string"
34
34
  paranoid_configuration.merge!(deleted_value: "deleted")
35
- elsif options[:column_type] == "boolean" && !options[:allow_nulls]
36
- paranoid_configuration.merge!(recovery_value: false)
37
- elsif options[:column_type] == "boolean"
38
- paranoid_configuration.merge!(allow_nulls: true)
39
35
  end
40
36
 
41
37
  paranoid_configuration.merge!(options) # user options
42
38
 
43
39
  unless %w[time boolean string].include? paranoid_configuration[:column_type]
44
- raise ArgumentError, "'time', 'boolean' or 'string' expected" \
45
- " for :column_type option, got #{paranoid_configuration[:column_type]}"
40
+ raise ArgumentError,
41
+ "'time', 'boolean' or 'string' expected for :column_type option," \
42
+ " got #{paranoid_configuration[:column_type]}"
46
43
  end
47
44
 
48
45
  return if paranoid?
@@ -67,3 +64,6 @@ ActiveRecord::Relation.include ActsAsParanoid::Relation
67
64
 
68
65
  # Push the recover callback onto the activerecord callback list
69
66
  ActiveRecord::Callbacks::CALLBACKS.push(:before_recover, :after_recover)
67
+
68
+ ActiveRecord::Reflection::AssociationReflection
69
+ .prepend ActsAsParanoid::AssociationReflection