delete_recursively 1.0.2 → 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: 8dd51a14780db543c8f623dcc7d65a1c64a0711c43e9f903b2dd2c1003290145
4
- data.tar.gz: ccaac450fe4293eb267bc0570cc055b097bf78d7f179349a351798578c828e57
3
+ metadata.gz: ffee1566019b0d29f6ae829b45b0ff3ecef4fcbe05dca62d4dc592f6d3c4b13b
4
+ data.tar.gz: 3ee5fbef69a228c8195d4312d24df37ec742c91821598b89041bd002f5b79182
5
5
  SHA512:
6
- metadata.gz: 604ba86881bd30346afa016ff584e1f94b8fa100ea5157e58750e3ff4fce9eb29ba24202b2c84ea1e3bcb0ddcfcac4abf0c5f60f093c6359bc121a952442dc87
7
- data.tar.gz: 61307162f1cf6f673371d04c32d75b6c920a653254c6d54d3e6806abfc059336a17437b2bd523acd8669de0a9e77506275bb341cd88ce8e84221ee1486832f68
6
+ metadata.gz: ffaf462ba56e68682f10540b6e972017e5ded7da75d95644a74a1bd8b2dc962cf84c85aac2561bc3663e121c61731e9bfab497e8329417a4db7254e32fdaa4fa
7
+ data.tar.gz: 640d3df7bf66b5a7c1ba5a5a42d20f297de30f82e77644c85f6325746bb28a0e35c1c475dab375ac5013be838d14229d496475cd30ffa828f96d2ef20ed0d5a9
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeleteRecursively
4
- VERSION = '1.0.2'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -21,34 +21,78 @@ module DeleteRecursively
21
21
  # override Association#handle_dependency to apply the new option if it is set
22
22
  module DependencyHandling
23
23
  def handle_dependency
24
- return super unless DeleteRecursively.enabled_for?(self)
24
+ if DeleteRecursively.enabled_for?(self)
25
+ delete_dependencies_recursively
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def delete_dependencies_recursively(force: false)
25
32
  if reflection.belongs_to?
26
- # can't use ::dependent_ids, owner is already destroyed at this point
27
- ids = load_target ? target.id : []
33
+ # Special case. The owner is already destroyed at this point,
34
+ # so we cannot use the efficient ::dependent_ids lookup. Note that this
35
+ # only happens for a single entry-record on #destroy, though.
36
+ return unless target = load_target
37
+
38
+ DeleteRecursively.delete_records_recursively(target.class, target.id, force: force)
28
39
  else
29
- ids = DeleteRecursively.dependent_ids(owner.class, owner.id, reflection)
40
+ DeleteRecursively.delete_recursively(reflection, owner.class, owner.id, force: force)
30
41
  end
31
- DeleteRecursively.delete_recursively(reflection, ids)
32
42
  end
33
43
  end
34
44
 
35
45
  class << self
36
- def delete_recursively(reflection, record_ids)
37
- record_class = reflection.klass
38
- if recurse_on?(reflection)
39
- record_class.reflect_on_all_associations.each do |sub_reflection|
40
- sub_ref_ids = dependent_ids(record_class, record_ids, sub_reflection)
41
- delete_recursively(sub_reflection, sub_ref_ids)
46
+ def delete_recursively(reflection, owner_class, owner_ids, seen: [], force: false)
47
+ # Dependent deletion can be bi-directional, so we need to avoid a loop.
48
+ return if seen.include?(reflection)
49
+
50
+ seen << reflection
51
+
52
+ associated_classes(reflection).each do |record_class|
53
+ record_ids = nil # fetched only when needed for recursion, deletion, or both
54
+
55
+ if recurse_on?(reflection)
56
+ record_ids = dependent_ids(owner_class, owner_ids, reflection, record_class)
57
+ record_class.reflect_on_all_associations.each do |subref|
58
+ delete_recursively(subref, record_class, record_ids, seen: seen, force: force)
59
+ end
60
+ end
61
+
62
+ if dest_method = destructive_method(reflection, record_class, record_ids, force: force)
63
+ record_ids ||= dependent_ids(owner_class, owner_ids, reflection, record_class)
64
+ record_class.send(dest_method, record_ids)
65
+ end
66
+ end
67
+ end
68
+
69
+ def associated_classes(reflection)
70
+ if reflection.polymorphic?
71
+ # This ignores relatives where the inverse relation is not defined.
72
+ # The alternative to this approach would be to expensively select
73
+ # all distinct values from the *_type column:
74
+ # reflection.active_record.distinct.pluck(reflection.foreign_type)
75
+ ActiveRecord::Base.descendants.select do |klass|
76
+ klass.reflect_on_all_associations
77
+ .any? { |ref| ref.inverse_of == reflection }
42
78
  end
79
+ else
80
+ [reflection.klass]
43
81
  end
44
- destroy_or_delete(reflection, record_class, record_ids)
45
82
  end
46
83
 
47
- def destroy_or_delete(reflection, record_class, record_ids)
48
- if destructive?(reflection)
49
- record_class.destroy(record_ids)
50
- elsif enabled_for?(reflection) || deleting?(reflection)
51
- record_class.delete(record_ids)
84
+ def delete_records_recursively(record_class, record_ids, force: false)
85
+ record_class.reflect_on_all_associations.each do |ref|
86
+ delete_recursively(ref, record_class, record_ids, force: force)
87
+ end
88
+ record_class.delete(record_ids)
89
+ end
90
+
91
+ def destructive_method(reflection, record_class, record_ids, force: false)
92
+ if deleting?(reflection) || force && destructive?(reflection)
93
+ :delete
94
+ elsif destructive?(reflection)
95
+ :destroy
52
96
  end
53
97
  end
54
98
 
@@ -65,16 +109,19 @@ module DeleteRecursively
65
109
  end
66
110
 
67
111
  def deleting?(reflection)
68
- %i[delete delete_all].include?(reflection.options[:dependent])
112
+ [:delete, :delete_all, NEW_DEPENDENT_OPTION].include?(reflection.options[:dependent])
69
113
  end
70
114
 
71
- def dependent_ids(owner_class, owner_ids, reflection)
115
+ def dependent_ids(owner_class, owner_ids, reflection, assoc_class = nil)
72
116
  if reflection.belongs_to?
73
- owners_arel = owner_class.where(owner_class.primary_key => owner_ids)
74
- owners_arel.pluck(reflection.association_foreign_key).compact
117
+ owners = owner_class.where(owner_class.primary_key => owner_ids)
118
+ if reflection.polymorphic?
119
+ owners = owners.where(reflection.foreign_type => assoc_class.to_s)
120
+ end
121
+ owners.pluck(reflection.foreign_key).compact
75
122
  elsif reflection.through_reflection
76
123
  dependent_ids_with_through_option(owner_class, owner_ids, reflection)
77
- else
124
+ else # plain `has_many` or `has_one`
78
125
  owner_foreign_key = foreign_key(owner_class, reflection)
79
126
  reflection.klass.where(owner_foreign_key => owner_ids).ids
80
127
  end
@@ -102,16 +149,21 @@ module DeleteRecursively
102
149
  end
103
150
 
104
151
  def foreign_key(owner_class, reflection)
105
- custom_foreign_key = reflection && reflection.options[:foreign_key]
106
- custom_foreign_key || owner_class.to_s.foreign_key
152
+ reflection && reflection.foreign_key || owner_class.to_s.foreign_key
107
153
  end
108
154
 
109
- def all(record_class, criteria = {})
155
+ def all(record_class, criteria = {}, seen = [])
156
+ return if seen.include?(record_class)
157
+
158
+ seen << record_class
159
+
110
160
  record_class.reflect_on_all_associations.each do |reflection|
111
- if recurse_on?(reflection)
112
- all(reflection.klass, criteria)
113
- elsif deleting?(reflection)
114
- delete_with_applicable_criteria(reflection.klass, criteria)
161
+ associated_classes(reflection).each do |assoc_class|
162
+ if recurse_on?(reflection)
163
+ all(assoc_class, criteria, seen)
164
+ elsif deleting?(reflection)
165
+ delete_with_applicable_criteria(assoc_class, criteria)
166
+ end
115
167
  end
116
168
  end
117
169
  delete_with_applicable_criteria(record_class, criteria)
@@ -128,10 +180,26 @@ end
128
180
 
129
181
  require 'active_record'
130
182
 
131
- %w[BelongsTo HasMany HasOne].each do |assoc_name|
132
- assoc_builder = ActiveRecord::Associations::Builder.const_get(assoc_name)
133
- assoc_builder.singleton_class.prepend(DeleteRecursively::OptionPermission)
183
+ module ActiveRecord
184
+ module Associations
185
+ %w[BelongsTo HasMany HasOne].each do |assoc_name|
186
+ assoc_builder = Builder.const_get(assoc_name)
187
+ assoc_builder.singleton_class.prepend(DeleteRecursively::OptionPermission)
134
188
 
135
- assoc_class = ActiveRecord::Associations.const_get("#{assoc_name}Association")
136
- assoc_class.prepend(DeleteRecursively::DependencyHandling)
189
+ assoc_class = const_get("#{assoc_name}Association")
190
+ assoc_class.prepend(DeleteRecursively::DependencyHandling)
191
+ end
192
+ end
193
+
194
+ class Base
195
+ def delete_recursively(force: false)
196
+ DeleteRecursively.delete_records_recursively(self.class, id, force: force)
197
+ end
198
+ end
199
+
200
+ class Relation
201
+ def delete_all_recursively(force: false)
202
+ DeleteRecursively.delete_records_recursively(klass, ids, force: force)
203
+ end
204
+ end
137
205
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delete_recursively
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janosch Müller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-06 00:00:00.000000000 Z
11
+ date: 2022-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -162,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
162
  - !ruby/object:Gem::Version
163
163
  version: '0'
164
164
  requirements: []
165
- rubygems_version: 3.2.32
165
+ rubygems_version: 3.4.0.dev
166
166
  signing_key:
167
167
  specification_version: 4
168
168
  summary: Delete ActiveRecords efficiently