bulk_dependency_eraser 1.2.2 → 1.3.2

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: 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