bulk_dependency_eraser 1.2.2 → 1.3.1
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60f1df6c3a60ce0e7d3048cdcb263986148ca7989464f90859455afea67eae78
|
4
|
+
data.tar.gz: 5069d8a435cc8ac13a2b4e873318c1c0a4ada041a5ed292b8df58212af3bc887
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 590c7dc30ca2d04140332d0c97128dcddbfb9a95e07d8a413006faa32011d3343fa5363e1a5a43f3678ae2b96f7ca9e44cdc23775f69a6971d576f000e4254c3
|
7
|
+
data.tar.gz: 6bff0c448928f611a0c8ce7f684df5b636d66bf0570c793ced8204eb759a1e0ad020e3947caa562610a5410f967b8ee4ef2bdc191aa37abd2e5ec47d497d11c6
|
@@ -13,10 +13,13 @@ module BulkDependencyEraser
|
|
13
13
|
# Won't parse any table in this list
|
14
14
|
ignore_tables_and_dependencies: [],
|
15
15
|
ignore_klass_names_and_dependencies: [],
|
16
|
+
disable_batching: false,
|
16
17
|
# a general batching size
|
17
18
|
batch_size: 10_000,
|
18
19
|
# A specific batching size for this class, overrides the batch_size
|
19
20
|
read_batch_size: nil,
|
21
|
+
# A specific read batching disable option
|
22
|
+
disable_read_batching: nil,
|
20
23
|
}.freeze
|
21
24
|
|
22
25
|
DEFAULT_DB_WRAPPER = ->(block) do
|
@@ -85,6 +88,27 @@ module BulkDependencyEraser
|
|
85
88
|
end
|
86
89
|
end
|
87
90
|
|
91
|
+
# Reverse order IDs
|
92
|
+
# - if a class self-referenced itself the children will be deleted/nullified before parent
|
93
|
+
deletion_list.keys.each do |class_name|
|
94
|
+
deletion_list[class_name] = deletion_list.delete(class_name).reverse
|
95
|
+
|
96
|
+
# TODO: Hard to test if not sorted
|
97
|
+
deletion_list[class_name] = deletion_list[class_name].sort if Rails.env.test?
|
98
|
+
end
|
99
|
+
|
100
|
+
nullification_list.keys.each do |class_name|
|
101
|
+
columns_and_ids = nullification_list.delete(class_name)
|
102
|
+
# we don't need to reverse the column keys
|
103
|
+
columns_and_ids.keys.each do |column_name|
|
104
|
+
columns_and_ids[column_name] = columns_and_ids.delete(column_name).reverse
|
105
|
+
|
106
|
+
# TODO: Hard to test if not sorted
|
107
|
+
columns_and_ids[column_name] = columns_and_ids.delete(column_name).sort if Rails.env.test?
|
108
|
+
end
|
109
|
+
nullification_list[class_name] = columns_and_ids
|
110
|
+
end
|
111
|
+
|
88
112
|
return build_result
|
89
113
|
end
|
90
114
|
|
@@ -127,8 +151,37 @@ module BulkDependencyEraser
|
|
127
151
|
attr_reader :table_names_to_parsed_klass_names
|
128
152
|
attr_reader :ignore_table_name_and_dependencies, :ignore_klass_name_and_dependencies
|
129
153
|
|
154
|
+
def pluck_from_query query, column = :id
|
155
|
+
query_ids = []
|
156
|
+
read_from_db do
|
157
|
+
# If the query has a limit, then we don't want to clobber with batching.
|
158
|
+
if batching_disabled? || !query.where({}).limit_value.nil?
|
159
|
+
# query without batching
|
160
|
+
query_ids = query.pluck(column)
|
161
|
+
else
|
162
|
+
# query with batching
|
163
|
+
offset = 0
|
164
|
+
loop do
|
165
|
+
new_query_ids = query.offset(offset).limit(batch_size).pluck(column)
|
166
|
+
query_ids += new_query_ids
|
167
|
+
|
168
|
+
break if new_query_ids.size < batch_size
|
169
|
+
|
170
|
+
# Move to the next batch
|
171
|
+
offset += batch_size
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
return query_ids
|
177
|
+
end
|
178
|
+
|
130
179
|
def batch_size
|
131
|
-
opts_c.read_batch_size
|
180
|
+
opts_c.read_batch_size.nil? ? opts_c.batch_size : opts_c.read_batch_size
|
181
|
+
end
|
182
|
+
|
183
|
+
def batching_disabled?
|
184
|
+
opts_c.disable_read_batching.nil? ? opts_c.disable_batching : opts_c.disable_read_batching
|
132
185
|
end
|
133
186
|
|
134
187
|
def deletion_query_parser query, association_parent = nil
|
@@ -176,13 +229,8 @@ module BulkDependencyEraser
|
|
176
229
|
return
|
177
230
|
end
|
178
231
|
|
179
|
-
# Pluck IDs of the current query
|
180
|
-
query_ids =
|
181
|
-
query.in_batches(of: batch_size) do |batch|
|
182
|
-
read_from_db do
|
183
|
-
query_ids += batch.pluck(:id)
|
184
|
-
end
|
185
|
-
end
|
232
|
+
# Pluck IDs of the current query
|
233
|
+
query_ids = pluck_from_query(query)
|
186
234
|
|
187
235
|
deletion_list[klass_name] ||= []
|
188
236
|
|
@@ -203,7 +251,7 @@ module BulkDependencyEraser
|
|
203
251
|
|
204
252
|
# Hard to test if not sorted
|
205
253
|
# - if we had more advanced rspec matches, we could do away with this.
|
206
|
-
deletion_list[klass_name].sort! if Rails.env.test?
|
254
|
+
# deletion_list[klass_name].sort! if Rails.env.test?
|
207
255
|
|
208
256
|
# ignore associations that aren't a dependent destroyable type
|
209
257
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
@@ -370,12 +418,7 @@ module BulkDependencyEraser
|
|
370
418
|
# - handle primary key edge cases
|
371
419
|
# - The associations might not be using the primary_key of the klass table, but we can support that here.
|
372
420
|
if specified_primary_key && specified_primary_key&.to_s != 'id'
|
373
|
-
alt_primary_ids =
|
374
|
-
query.in_batches(of: batch_size) do |batch|
|
375
|
-
read_from_db do
|
376
|
-
alt_primary_ids += query.pluck(specified_primary_key)
|
377
|
-
end
|
378
|
-
end
|
421
|
+
alt_primary_ids = pluck_from_query(query, specified_primary_key)
|
379
422
|
assoc_query = assoc_query.where(specified_foreign_key.to_sym => alt_primary_ids)
|
380
423
|
else
|
381
424
|
assoc_query = assoc_query.where(specified_foreign_key.to_sym => query_ids)
|
@@ -397,12 +440,7 @@ module BulkDependencyEraser
|
|
397
440
|
elsif type == :nullify
|
398
441
|
# No need for recursion here.
|
399
442
|
# - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
400
|
-
assoc_ids =
|
401
|
-
assoc_query.in_batches(of: batch_size) do |batch|
|
402
|
-
read_from_db do
|
403
|
-
assoc_ids += batch.pluck(:id)
|
404
|
-
end
|
405
|
-
end
|
443
|
+
assoc_ids = pluck_from_query(assoc_query)
|
406
444
|
|
407
445
|
# No assoc_ids, no need to add it to the nullification list
|
408
446
|
return if assoc_ids.none?
|
@@ -412,7 +450,7 @@ module BulkDependencyEraser
|
|
412
450
|
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
413
451
|
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
414
452
|
|
415
|
-
nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
|
453
|
+
# nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
|
416
454
|
|
417
455
|
# Also nullify the 'type' field, if the association is polymorphic
|
418
456
|
if specified_foreign_type
|
@@ -420,7 +458,7 @@ module BulkDependencyEraser
|
|
420
458
|
nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
|
421
459
|
nullification_list[assoc_klass_name][specified_foreign_type].uniq!
|
422
460
|
|
423
|
-
nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
|
461
|
+
# nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
|
424
462
|
end
|
425
463
|
else
|
426
464
|
raise "invalid parsing type: #{type}"
|
@@ -508,23 +546,20 @@ module BulkDependencyEraser
|
|
508
546
|
return
|
509
547
|
end
|
510
548
|
|
511
|
-
query
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
)
|
516
|
-
end
|
549
|
+
foreign_keys = pluck_from_query(query, specified_foreign_key)
|
550
|
+
assoc_query = assoc_query.where(
|
551
|
+
specified_primary_key.to_sym => foreign_keys
|
552
|
+
)
|
517
553
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
end
|
525
|
-
else
|
526
|
-
raise "invalid parsing type: #{type}"
|
554
|
+
if type == :delete
|
555
|
+
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
556
|
+
deletion_query_parser(assoc_query, parent_class)
|
557
|
+
elsif type == :restricted
|
558
|
+
if traverse_restricted_dependency?(parent_class, reflection, assoc_query)
|
559
|
+
deletion_query_parser(assoc_query, parent_class)
|
527
560
|
end
|
561
|
+
else
|
562
|
+
raise "invalid parsing type: #{type}"
|
528
563
|
end
|
529
564
|
end
|
530
565
|
|
@@ -539,6 +574,8 @@ module BulkDependencyEraser
|
|
539
574
|
# In this case, it's like a `belongs_to :polymorphicable, polymorphic: true, dependent: :destroy` use-case
|
540
575
|
# - it's unusual, but valid use-case
|
541
576
|
def association_parser_belongs_to_polymorphic(parent_class, query, query_ids, association_name, type)
|
577
|
+
# raise "Unsupported use-case: #{parent_class.name} -> belongs_to :polymorphicable, polymorphic: true, dependent: :destroy"
|
578
|
+
|
542
579
|
reflection = parent_class.reflect_on_association(association_name)
|
543
580
|
reflection_type = reflection.class.name
|
544
581
|
|
@@ -576,30 +613,30 @@ module BulkDependencyEraser
|
|
576
613
|
return
|
577
614
|
end
|
578
615
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
616
|
+
# Not sure how to limit/offset batch this right now.
|
617
|
+
# - it's a rare use-case, let's just leave this as a TODO:
|
618
|
+
foreign_ids_by_type = read_from_db do
|
619
|
+
query.pluck(specified_foreign_key, specified_foreign_type).each_with_object({}) do |(id, type), hash|
|
620
|
+
hash.key?(type) ? hash[type] << id : hash[type] = [id]
|
584
621
|
end
|
622
|
+
end
|
585
623
|
|
586
|
-
|
624
|
+
if type == :delete
|
625
|
+
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
626
|
+
foreign_ids_by_type.each do |type, ids|
|
627
|
+
assoc_klass = type.constantize
|
628
|
+
deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
|
629
|
+
end
|
630
|
+
elsif type == :restricted
|
631
|
+
if traverse_restricted_dependency_for_belongs_to_poly?(parent_class, reflection, foreign_ids_by_type)
|
587
632
|
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
588
633
|
foreign_ids_by_type.each do |type, ids|
|
589
634
|
assoc_klass = type.constantize
|
590
635
|
deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
|
591
636
|
end
|
592
|
-
elsif type == :restricted
|
593
|
-
if traverse_restricted_dependency_for_belongs_to_poly?(parent_class, reflection, foreign_ids_by_type)
|
594
|
-
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
595
|
-
foreign_ids_by_type.each do |type, ids|
|
596
|
-
assoc_klass = type.constantize
|
597
|
-
deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
|
598
|
-
end
|
599
|
-
end
|
600
|
-
else
|
601
|
-
raise "invalid parsing type: #{type}"
|
602
637
|
end
|
638
|
+
else
|
639
|
+
raise "invalid parsing type: #{type}"
|
603
640
|
end
|
604
641
|
end
|
605
642
|
|
@@ -5,10 +5,13 @@ module BulkDependencyEraser
|
|
5
5
|
db_delete_wrapper: self::DEFAULT_DB_WRAPPER,
|
6
6
|
# Set to true if you want 'ActiveRecord::InvalidForeignKey' errors raised during deletions
|
7
7
|
enable_invalid_foreign_key_detection: false,
|
8
|
+
disable_batching: false,
|
8
9
|
# a general batching size
|
9
10
|
batch_size: 300,
|
10
11
|
# A specific batching size for this class, overrides the batch_size
|
11
12
|
delete_batch_size: nil,
|
13
|
+
# A specific batching size for this class, overrides the batch_size
|
14
|
+
disable_delete_batching: nil,
|
12
15
|
}.freeze
|
13
16
|
|
14
17
|
DEFAULT_DB_WRAPPER = ->(block) do
|
@@ -30,6 +33,7 @@ module BulkDependencyEraser
|
|
30
33
|
ActiveRecord::Base.transaction do
|
31
34
|
current_class_name = 'N/A'
|
32
35
|
begin
|
36
|
+
# IDs should have already been reversed in builder
|
33
37
|
class_names_and_ids.keys.reverse.each do |class_name|
|
34
38
|
current_class_name = class_name
|
35
39
|
ids = class_names_and_ids[class_name]
|
@@ -65,11 +69,23 @@ module BulkDependencyEraser
|
|
65
69
|
opts_c.delete_batch_size || opts_c.batch_size
|
66
70
|
end
|
67
71
|
|
72
|
+
def batching_disabled?
|
73
|
+
opts_c.disable_delete_batching.nil? ? opts_c.disable_batching : opts_c.disable_delete_batching
|
74
|
+
end
|
75
|
+
|
68
76
|
def delete_by_klass_and_ids klass, ids
|
69
77
|
puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
|
70
|
-
|
78
|
+
if batching_disabled?
|
79
|
+
puts "Deleting without batching" if opts_c.verbose
|
71
80
|
delete_in_db do
|
72
|
-
klass.unscoped.where(id:
|
81
|
+
klass.unscoped.where(id: ids).delete_all
|
82
|
+
end
|
83
|
+
else
|
84
|
+
puts "Deleting with batching" if opts_c.verbose
|
85
|
+
ids.each_slice(batch_size) do |ids_subset|
|
86
|
+
delete_in_db do
|
87
|
+
klass.unscoped.where(id: ids_subset).delete_all
|
88
|
+
end
|
73
89
|
end
|
74
90
|
end
|
75
91
|
end
|
@@ -78,6 +94,5 @@ module BulkDependencyEraser
|
|
78
94
|
puts "Deleting from DB..." if opts_c.verbose
|
79
95
|
opts_c.db_delete_wrapper.call(block)
|
80
96
|
end
|
81
|
-
|
82
97
|
end
|
83
98
|
end
|
@@ -7,10 +7,13 @@ module BulkDependencyEraser
|
|
7
7
|
# - I can't think of a use-case where a nullification would generate an invalid key error
|
8
8
|
# - Not hurting anything to leave it in, but might remove it in the future.
|
9
9
|
enable_invalid_foreign_key_detection: false,
|
10
|
+
disable_batching: false,
|
10
11
|
# a general batching size
|
11
12
|
batch_size: 300,
|
12
13
|
# A specific batching size for this class, overrides the batch_size
|
13
14
|
nullify_batch_size: nil,
|
15
|
+
# A specific batching size for this class, overrides the batch_size
|
16
|
+
disable_nullify_batching: nil,
|
14
17
|
}.freeze
|
15
18
|
|
16
19
|
DEFAULT_DB_WRAPPER = ->(block) do
|
@@ -84,11 +87,12 @@ module BulkDependencyEraser
|
|
84
87
|
current_class_name = 'N/A'
|
85
88
|
current_column = 'N/A'
|
86
89
|
begin
|
90
|
+
# column_and_ids should have already been reversed in builder
|
87
91
|
class_names_columns_and_ids.keys.reverse.each do |class_name|
|
88
92
|
current_class_name = class_name
|
89
93
|
klass = class_name.constantize
|
90
|
-
|
91
94
|
columns_and_ids = class_names_columns_and_ids[class_name]
|
95
|
+
|
92
96
|
columns_and_ids.each do |column, ids|
|
93
97
|
current_column = column
|
94
98
|
|
@@ -123,6 +127,10 @@ module BulkDependencyEraser
|
|
123
127
|
opts_c.nullify_batch_size || opts_c.batch_size
|
124
128
|
end
|
125
129
|
|
130
|
+
def batching_disabled?
|
131
|
+
opts_c.disable_nullify_batching.nil? ? opts_c.disable_batching : opts_c.disable_nullify_batching
|
132
|
+
end
|
133
|
+
|
126
134
|
def nullify_by_klass_column_and_ids klass, columns, ids
|
127
135
|
nullify_columns = {}
|
128
136
|
|
@@ -135,9 +143,15 @@ module BulkDependencyEraser
|
|
135
143
|
nullify_columns[columns] = nil
|
136
144
|
end
|
137
145
|
|
138
|
-
|
146
|
+
if batching_disabled?
|
139
147
|
nullify_in_db do
|
140
|
-
klass.unscoped.where(id:
|
148
|
+
klass.unscoped.where(id: ids).update_all(nullify_columns)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
ids.each_slice(batch_size) do |ids_subset|
|
152
|
+
nullify_in_db do
|
153
|
+
klass.unscoped.where(id: ids_subset).update_all(nullify_columns)
|
154
|
+
end
|
141
155
|
end
|
142
156
|
end
|
143
157
|
end
|