bulk_dependency_eraser 1.2.2 → 1.3.2

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: 80c6e41e13092670b6f67fc6cdb8d117c0222b0d2ce7c0043e1f12dcf01c1273
4
- data.tar.gz: 9b93602042b1863caa6a1eac272547d76559e7ba62e97e29831c5d171050eafe
3
+ metadata.gz: 7e0b2a7c10501166d33e7076cf19b7c241ffd9de2704d94aca6f7c9fecc6e2d4
4
+ data.tar.gz: 6788ed915a4a6b4f7d7dbe2c8387244b0ff5e758098fe48f47b47127e64d9fb5
5
5
  SHA512:
6
- metadata.gz: 26e795e7ecf8727eacf36267502454478922430dc86424f6ef0483bb195a09dc1c52c47067c0a87d70f9235173715a975422548ebd50da7fb1cf70ccbc7f0721
7
- data.tar.gz: e6722b7ca1fd1c58f1b4a9d619af965eddb440c159bc2b9fd6efd436c2e927fd6e58679bb25f71438d24987aea8726ceb69a5ee081654e6cb8fbf4336d9c7dce
6
+ metadata.gz: ab13a12a9e5965890ae2e27cbf0971ba75aec2dbdc463d92c1631ac9cdb6dd98729e22cedb9c05cfb4cd4f897ca774c5204d0c3a61c804f8f7f8961de4e686cb
7
+ data.tar.gz: 9d0968970dd212e9e07c459134e829c8b1fd17c3c65d2b1e7665f983a68c4655044e3732ce7e2f6d28fb0c0e59606ffde0f343761a4730abb2f6dc74c3ba521d
@@ -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,40 @@ 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
+ # ordering shouldn't matter in these queries, and would slow it down
156
+ # - we're ignoring default_scope ordering, but assoc-defined ordering would still take effect
157
+ query = query.reorder('')
158
+ query_ids = []
159
+ read_from_db do
160
+ # If the query has a limit, then we don't want to clobber with batching.
161
+ if batching_disabled? || !query.where({}).limit_value.nil?
162
+ # query without batching
163
+ query_ids = query.pluck(column)
164
+ else
165
+ # query with batching
166
+ offset = 0
167
+ loop do
168
+ new_query_ids = query.offset(offset).limit(batch_size).pluck(column)
169
+ query_ids += new_query_ids
170
+
171
+ break if new_query_ids.size < batch_size
172
+
173
+ # Move to the next batch
174
+ offset += batch_size
175
+ end
176
+ end
177
+ end
178
+
179
+ return query_ids
180
+ end
181
+
130
182
  def batch_size
131
- opts_c.read_batch_size || opts_c.batch_size
183
+ opts_c.read_batch_size.nil? ? opts_c.batch_size : opts_c.read_batch_size
184
+ end
185
+
186
+ def batching_disabled?
187
+ opts_c.disable_read_batching.nil? ? opts_c.disable_batching : opts_c.disable_read_batching
132
188
  end
133
189
 
134
190
  def deletion_query_parser query, association_parent = nil
@@ -176,13 +232,8 @@ module BulkDependencyEraser
176
232
  return
177
233
  end
178
234
 
179
- # Pluck IDs of the current query (batchified)
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
235
+ # Pluck IDs of the current query
236
+ query_ids = pluck_from_query(query)
186
237
 
187
238
  deletion_list[klass_name] ||= []
188
239
 
@@ -203,7 +254,7 @@ module BulkDependencyEraser
203
254
 
204
255
  # Hard to test if not sorted
205
256
  # - if we had more advanced rspec matches, we could do away with this.
206
- deletion_list[klass_name].sort! if Rails.env.test?
257
+ # deletion_list[klass_name].sort! if Rails.env.test?
207
258
 
208
259
  # ignore associations that aren't a dependent destroyable type
209
260
  destroy_associations = query.reflect_on_all_associations.select do |reflection|
@@ -370,12 +421,7 @@ module BulkDependencyEraser
370
421
  # - handle primary key edge cases
371
422
  # - The associations might not be using the primary_key of the klass table, but we can support that here.
372
423
  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
424
+ alt_primary_ids = pluck_from_query(query, specified_primary_key)
379
425
  assoc_query = assoc_query.where(specified_foreign_key.to_sym => alt_primary_ids)
380
426
  else
381
427
  assoc_query = assoc_query.where(specified_foreign_key.to_sym => query_ids)
@@ -397,12 +443,7 @@ module BulkDependencyEraser
397
443
  elsif type == :nullify
398
444
  # No need for recursion here.
399
445
  # - 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
446
+ assoc_ids = pluck_from_query(assoc_query)
406
447
 
407
448
  # No assoc_ids, no need to add it to the nullification list
408
449
  return if assoc_ids.none?
@@ -412,7 +453,7 @@ module BulkDependencyEraser
412
453
  nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
413
454
  nullification_list[assoc_klass_name][specified_foreign_key].uniq!
414
455
 
415
- nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
456
+ # nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
416
457
 
417
458
  # Also nullify the 'type' field, if the association is polymorphic
418
459
  if specified_foreign_type
@@ -420,7 +461,7 @@ module BulkDependencyEraser
420
461
  nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
421
462
  nullification_list[assoc_klass_name][specified_foreign_type].uniq!
422
463
 
423
- nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
464
+ # nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
424
465
  end
425
466
  else
426
467
  raise "invalid parsing type: #{type}"
@@ -508,23 +549,20 @@ module BulkDependencyEraser
508
549
  return
509
550
  end
510
551
 
511
- query.in_batches(of: batch_size) do |batch|
512
- assoc_batch_query = read_from_db do
513
- assoc_query.where(
514
- specified_primary_key.to_sym => batch.pluck(specified_foreign_key)
515
- )
516
- end
552
+ foreign_keys = pluck_from_query(query, specified_foreign_key)
553
+ assoc_query = assoc_query.where(
554
+ specified_primary_key.to_sym => foreign_keys
555
+ )
517
556
 
518
- if type == :delete
519
- # Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
520
- deletion_query_parser(assoc_batch_query, parent_class)
521
- elsif type == :restricted
522
- if traverse_restricted_dependency?(parent_class, reflection, assoc_batch_query)
523
- deletion_query_parser(assoc_batch_query, parent_class)
524
- end
525
- else
526
- raise "invalid parsing type: #{type}"
557
+ if type == :delete
558
+ # Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
559
+ deletion_query_parser(assoc_query, parent_class)
560
+ elsif type == :restricted
561
+ if traverse_restricted_dependency?(parent_class, reflection, assoc_query)
562
+ deletion_query_parser(assoc_query, parent_class)
527
563
  end
564
+ else
565
+ raise "invalid parsing type: #{type}"
528
566
  end
529
567
  end
530
568
 
@@ -539,6 +577,8 @@ module BulkDependencyEraser
539
577
  # In this case, it's like a `belongs_to :polymorphicable, polymorphic: true, dependent: :destroy` use-case
540
578
  # - it's unusual, but valid use-case
541
579
  def association_parser_belongs_to_polymorphic(parent_class, query, query_ids, association_name, type)
580
+ # raise "Unsupported use-case: #{parent_class.name} -> belongs_to :polymorphicable, polymorphic: true, dependent: :destroy"
581
+
542
582
  reflection = parent_class.reflect_on_association(association_name)
543
583
  reflection_type = reflection.class.name
544
584
 
@@ -576,30 +616,47 @@ module BulkDependencyEraser
576
616
  return
577
617
  end
578
618
 
579
- query.in_batches(of: batch_size) do |batch|
580
- foreign_ids_by_type = read_from_db do
581
- batch.pluck(specified_foreign_key, specified_foreign_type).each_with_object({}) do |(id, type), hash|
619
+ foreign_ids_by_type = read_from_db do
620
+ if batching_disabled? || !query.where({}).limit_value.nil?
621
+ # query without batching
622
+ query.reorder('').pluck(specified_foreign_key, specified_foreign_type).each_with_object({}) do |(id, type), hash|
582
623
  hash.key?(type) ? hash[type] << id : hash[type] = [id]
583
624
  end
625
+ else
626
+ columns_and_ids = {}
627
+ offset = 0
628
+ loop do
629
+ counter = 0
630
+ query.reorder('').offset(offset).limit(batch_size).pluck(specified_foreign_key, specified_foreign_type).each do |id, type|
631
+ columns_and_ids.key?(type) ? columns_and_ids[type] << id : columns_and_ids[type] = [id]
632
+ counter += 1
633
+ end
634
+
635
+ break if counter < batch_size
636
+
637
+ # Move to the next batch
638
+ offset += batch_size
639
+ end
640
+ columns_and_ids
584
641
  end
642
+ end
585
643
 
586
- if type == :delete
644
+ if type == :delete
645
+ # Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
646
+ foreign_ids_by_type.each do |type, ids|
647
+ assoc_klass = type.constantize
648
+ deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
649
+ end
650
+ elsif type == :restricted
651
+ if traverse_restricted_dependency_for_belongs_to_poly?(parent_class, reflection, foreign_ids_by_type)
587
652
  # Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
588
653
  foreign_ids_by_type.each do |type, ids|
589
654
  assoc_klass = type.constantize
590
655
  deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
591
656
  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
657
  end
658
+ else
659
+ raise "invalid parsing type: #{type}"
603
660
  end
604
661
  end
605
662
 
@@ -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
- ids.each_slice(batch_size) do |ids_subset|
78
+ if batching_disabled?
79
+ puts "Deleting without batching" if opts_c.verbose
71
80
  delete_in_db do
72
- klass.unscoped.where(id: ids_subset).delete_all
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
- ids.each_slice(batch_size) do |ids_subset|
146
+ if batching_disabled?
139
147
  nullify_in_db do
140
- klass.unscoped.where(id: ids_subset).update_all(nullify_columns)
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
@@ -1,3 +1,3 @@
1
1
  module BulkDependencyEraser
2
- VERSION = "1.2.2".freeze
2
+ VERSION = "1.3.2".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulk_dependency_eraser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin.dana.software.dev@gmail.com