bulk_dependency_eraser 0.0.1 → 1.0.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 +4 -4
- data/lib/bulk_dependency_eraser/builder.rb +65 -15
- data/lib/bulk_dependency_eraser/deleter.rb +23 -6
- data/lib/bulk_dependency_eraser/manager.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cafe34b586a54a169d2d728ba0a39d9403c90796db9f40c9050620eaa4ef156
|
4
|
+
data.tar.gz: a1a7001243a75db7740e2fbcbdfbf43dad5253f83de8c757bb7b2d0ab2f57763
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fc46dd24138b1be6c2ff436e9afdaa4f4f877c37fe23bec00ffec8ce142ffda71c6cccefd7a5d8354986757f9df02f0c64918419d270471eea866226cce87a6
|
7
|
+
data.tar.gz: ecfa3aca0fdae9d2de444f8fb8427758420c8ffa7ae8370daff95b5ef975db83ec9fc573e91d83e93f84acfd11e117171a1a276b45841954526cba6c481d2b21
|
@@ -6,7 +6,13 @@ module BulkDependencyEraser
|
|
6
6
|
# Some associations scopes take parameters.
|
7
7
|
# - We would have to instantiate if we wanted to apply that scope filter.
|
8
8
|
instantiate_if_assoc_scope_with_arity: false,
|
9
|
+
# wraps around the DB reading
|
9
10
|
db_read_wrapper: self::DEFAULT_DB_WRAPPER,
|
11
|
+
# Will parse these tables and their dependencies, but will remove the tables from the lists after parsing.
|
12
|
+
ignore_tables: [],
|
13
|
+
# Won't parse any table in this list
|
14
|
+
ignore_tables_and_dependencies: [],
|
15
|
+
ignore_klass_names_and_dependencies: [],
|
10
16
|
}.freeze
|
11
17
|
|
12
18
|
DEPENDENCY_NULLIFY = %i[
|
@@ -36,17 +42,44 @@ module BulkDependencyEraser
|
|
36
42
|
].freeze
|
37
43
|
|
38
44
|
attr_reader :deletion_list, :nullification_list
|
45
|
+
attr_reader :ignore_table_deletion_list, :ignore_table_nullification_list
|
39
46
|
|
40
47
|
def initialize query:, opts: {}
|
41
48
|
@query = query
|
42
49
|
@deletion_list = {}
|
43
50
|
@nullification_list = {}
|
51
|
+
|
52
|
+
# For any ignored table results, they will be stored here
|
53
|
+
@ignore_table_deletion_list = {}
|
54
|
+
@ignore_table_nullification_list = {}
|
55
|
+
|
56
|
+
@table_names_to_parsed_klass_names = {}
|
57
|
+
|
44
58
|
super(opts:)
|
59
|
+
|
60
|
+
@ignore_table_name_and_dependencies = opts_c.ignore_tables_and_dependencies.collect { |table_name| table_name }
|
61
|
+
@ignore_klass_name_and_dependencies = opts_c.ignore_klass_names_and_dependencies.collect { |klass_name| klass_name }
|
45
62
|
end
|
46
63
|
|
47
64
|
def execute
|
65
|
+
# go through deletion/nullification lists and remove any tables from 'ignore_tables' option
|
66
|
+
build_result = build
|
67
|
+
|
68
|
+
# move any klass names if told to ignore them into their respective new lists
|
69
|
+
# - prior approach was to use table_name.classify, but we can't trust that approach.
|
70
|
+
opts_c.ignore_tables.each do |table_name|
|
71
|
+
table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
|
72
|
+
ignore_table_deletion_list[klass_name] = deletion_list.delete(klass_name) if deletion_list.key?(klass_name)
|
73
|
+
ignore_table_nullification_list[klass_name] = nullification_list.delete(klass_name) if nullification_list.key?(klass_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
return build_result
|
78
|
+
end
|
79
|
+
|
80
|
+
def build
|
48
81
|
begin
|
49
|
-
|
82
|
+
deletion_query_parser(@query)
|
50
83
|
|
51
84
|
uniqify_errors!
|
52
85
|
|
@@ -80,12 +113,26 @@ module BulkDependencyEraser
|
|
80
113
|
if query.is_a?(ActiveRecord::Relation)
|
81
114
|
klass = query.klass
|
82
115
|
klass_name = query.klass.name
|
83
|
-
table_klass_name = query.klass.table_name.classify
|
84
116
|
else
|
85
117
|
# current_query is a normal rails class
|
86
118
|
klass = query
|
87
119
|
klass_name = query.name
|
88
|
-
|
120
|
+
end
|
121
|
+
|
122
|
+
table_names_to_parsed_klass_names[klass.table_name] ||= []
|
123
|
+
# Need to populate this list here, so we can have access to it later for the :ignore_tables option
|
124
|
+
unless table_names_to_parsed_klass_names[klass.table_name].include?(klass_name)
|
125
|
+
table_names_to_parsed_klass_names[klass.table_name] << klass_name
|
126
|
+
end
|
127
|
+
|
128
|
+
if ignore_table_name_and_dependencies.include?(klass.table_name)
|
129
|
+
# Not parsing, table and dependencies ignorable
|
130
|
+
return
|
131
|
+
end
|
132
|
+
|
133
|
+
if ignore_klass_name_and_dependencies.include?(klass_name)
|
134
|
+
# Not parsing, table and dependencies ignorable
|
135
|
+
return
|
89
136
|
end
|
90
137
|
|
91
138
|
if opts_c.verbose
|
@@ -108,25 +155,25 @@ module BulkDependencyEraser
|
|
108
155
|
query.pluck(:id)
|
109
156
|
end
|
110
157
|
|
111
|
-
deletion_list[
|
158
|
+
deletion_list[klass_name] ||= []
|
112
159
|
|
113
160
|
# prevent infinite recursion here.
|
114
161
|
# - Remove any IDs that have been processed before
|
115
|
-
query_ids = query_ids - deletion_list[
|
162
|
+
query_ids = query_ids - deletion_list[klass_name]
|
116
163
|
# If ids are nil, let's find that error
|
117
164
|
if query_ids.none? #|| query_ids.nil?
|
118
165
|
# quick cleanup, if turns out was an empty class
|
119
|
-
deletion_list.delete(
|
166
|
+
deletion_list.delete(klass_name) if deletion_list[klass_name].none?
|
120
167
|
return
|
121
168
|
end
|
122
169
|
|
123
170
|
# Use-case: We have more IDs to process
|
124
171
|
# - can now safely add to the list, since we've prevented infinite recursion
|
125
|
-
deletion_list[
|
172
|
+
deletion_list[klass_name] += query_ids
|
126
173
|
|
127
174
|
# Hard to test if not sorted
|
128
175
|
# - if we had more advanced rspec matches, we could do away with this.
|
129
|
-
deletion_list[
|
176
|
+
deletion_list[klass_name].sort! if Rails.env.test?
|
130
177
|
|
131
178
|
# ignore associations that aren't a dependent destroyable type
|
132
179
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
@@ -176,7 +223,7 @@ module BulkDependencyEraser
|
|
176
223
|
reflection = parent_class.reflect_on_association(association_name)
|
177
224
|
reflection_type = reflection.class.name
|
178
225
|
assoc_klass = reflection.klass
|
179
|
-
|
226
|
+
assoc_klass_name = assoc_klass.name
|
180
227
|
|
181
228
|
assoc_query = assoc_klass.unscoped
|
182
229
|
|
@@ -247,7 +294,7 @@ module BulkDependencyEraser
|
|
247
294
|
deletion_query_parser(assoc_query, parent_class)
|
248
295
|
elsif type == :nullify
|
249
296
|
# No need for recursion here.
|
250
|
-
# - we're not destroying these assocs so we don't need to parse their dependencies.
|
297
|
+
# - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
251
298
|
assoc_ids = read_from_db do
|
252
299
|
assoc_query.pluck(:id)
|
253
300
|
end
|
@@ -255,11 +302,10 @@ module BulkDependencyEraser
|
|
255
302
|
# No assoc_ids, no need to add it to the nullification list
|
256
303
|
return if assoc_ids.none?
|
257
304
|
|
258
|
-
|
259
|
-
nullification_list[
|
260
|
-
nullification_list[
|
261
|
-
nullification_list[
|
262
|
-
nullification_list[assoc_table_klass_name][specified_foreign_key].uniq!
|
305
|
+
nullification_list[assoc_klass_name] ||= {}
|
306
|
+
nullification_list[assoc_klass_name][specified_foreign_key] ||= []
|
307
|
+
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
308
|
+
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
263
309
|
else
|
264
310
|
raise "invalid parsing type: #{type}"
|
265
311
|
end
|
@@ -267,6 +313,10 @@ module BulkDependencyEraser
|
|
267
313
|
|
268
314
|
protected
|
269
315
|
|
316
|
+
attr_reader :ignore_klass_and_dependencies
|
317
|
+
attr_reader :table_names_to_parsed_klass_names
|
318
|
+
attr_reader :ignore_table_name_and_dependencies, :ignore_klass_name_and_dependencies
|
319
|
+
|
270
320
|
# A dependent assoc may be through another association. Follow the throughs to find the correct assoc to destroy.
|
271
321
|
def find_root_association_from_through_assocs klass, association_name
|
272
322
|
reflection = klass.reflect_on_association(association_name)
|
@@ -3,6 +3,8 @@ module BulkDependencyEraser
|
|
3
3
|
DEFAULT_OPTS = {
|
4
4
|
verbose: false,
|
5
5
|
db_delete_wrapper: self::DEFAULT_DB_WRAPPER,
|
6
|
+
# Set to true if you want 'ActiveRecord::InvalidForeignKey' errors raised during deletions
|
7
|
+
enable_invalid_foreign_key_detection: false
|
6
8
|
}.freeze
|
7
9
|
|
8
10
|
def initialize class_names_and_ids: {}, opts: {}
|
@@ -11,6 +13,10 @@ module BulkDependencyEraser
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def execute
|
16
|
+
if opts_c.verbose && opts_c.enable_invalid_foreign_key_detection
|
17
|
+
puts "ActiveRecord::Base.connection.disable_referential_integrity - disabled!"
|
18
|
+
end
|
19
|
+
|
14
20
|
ActiveRecord::Base.transaction do
|
15
21
|
current_class_name = 'N/A'
|
16
22
|
begin
|
@@ -19,12 +25,16 @@ module BulkDependencyEraser
|
|
19
25
|
ids = class_names_and_ids[class_name]
|
20
26
|
klass = class_name.constantize
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
if opts_c.enable_invalid_foreign_key_detection
|
29
|
+
# delete with referential integrity
|
30
|
+
delete_by_klass_and_ids(klass, ids)
|
31
|
+
else
|
32
|
+
# delete without referential integrity
|
33
|
+
# Disable any ActiveRecord::InvalidForeignKey raised errors.
|
34
|
+
# - src: https://stackoverflow.com/questions/41005849/rails-migrations-temporarily-ignore-foreign-key-constraint
|
35
|
+
# https://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractAdapter/disable_referential_integrity
|
36
|
+
ActiveRecord::Base.connection.disable_referential_integrity do
|
37
|
+
delete_by_klass_and_ids(klass, ids)
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -39,6 +49,13 @@ module BulkDependencyEraser
|
|
39
49
|
|
40
50
|
protected
|
41
51
|
|
52
|
+
def delete_by_klass_and_ids klass, ids
|
53
|
+
puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
|
54
|
+
delete_in_db do
|
55
|
+
klass.unscoped.where(id: ids).delete_all
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
42
59
|
attr_reader :class_names_and_ids
|
43
60
|
|
44
61
|
def delete_in_db(&block)
|
@@ -5,6 +5,7 @@ module BulkDependencyEraser
|
|
5
5
|
}.freeze
|
6
6
|
|
7
7
|
delegate :nullification_list, :deletion_list, to: :dependency_builder
|
8
|
+
delegate :ignore_table_deletion_list, :ignore_table_nullification_list, to: :dependency_builder
|
8
9
|
|
9
10
|
# @param query [ActiveRecord::Base | ActiveRecord::Relation]
|
10
11
|
def initialize query:, opts: {}
|
@@ -62,7 +63,6 @@ module BulkDependencyEraser
|
|
62
63
|
unless nullifier_execution
|
63
64
|
puts "Nullifier execution FAILED" if opts_c.verbose
|
64
65
|
merge_errors(nullifier.errors, 'Nullifier: ')
|
65
|
-
@errors += nullifier.errors
|
66
66
|
else
|
67
67
|
puts "Nullifier execution SUCCESSFUL" if opts_c.verbose
|
68
68
|
end
|