bulk_dependency_eraser 4.0.0 → 4.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e215e610f3c90e10cb386499bea48adc2a12a6cc7ce2a35466de12d5dc2dbb1d
4
- data.tar.gz: 3c56acb8837e54c3387a6c2754dcab92e88402084f99198ab62c0ee1ea46d45b
3
+ metadata.gz: 16b8469147c79e15bbd947d75f9b9e7aa243f219be6796f516967a4f134609bd
4
+ data.tar.gz: a7a45c24562a67a1762d120caa9b4a14ab211594245383be4b21a70fa9f5a5e0
5
5
  SHA512:
6
- metadata.gz: c1a1b05c9fd919c87a4947409296d65129f82817722d2de883b5f09e873b0851364348face62dc96a2251bb2aa8c8cba678798e0083681c7fa94f3ad0e1b6df6
7
- data.tar.gz: 321cda6cc39a9ef3af66023aca8dbd385d46357ead943908d659880f968f2c7e1d5c9c8035d7f8c1c022b21e505aa25fc07be6a93296c73e73c67e0079061ff3
6
+ metadata.gz: 8ea34f3b1963b7c8d7720292a47edffb1351f18b6ecb6822e2965fe96244700044d9dcba8124cc088033d3114da0775b0a3be0c4636424af11c101e5e90fb686
7
+ data.tar.gz: fe4901ea4c7f99ef0e5228b94280f76b6102ef9686c2a8a6fa5fcb4da768fc0303e1da2929a1672bfea77ebcb0e42c9692c7774df21bf891d4e8054979d2c3ea
@@ -52,10 +52,13 @@ module BulkDependencyEraser
52
52
  # Applied to reading queries
53
53
  # - 1st priority of scopes
54
54
  reading_proc_scopes_per_class_name: {},
55
+ # Using PG system column CTID can result in a 10x deletion speed increase
56
+ # - is used in combination with the primary key column to ensure CTID hasn't changed.
57
+ use_pg_system_column_ctid: false,
55
58
  }.freeze
56
59
 
57
60
  # write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.
58
- attr_accessor :deletion_list, :nullification_list
61
+ attr_accessor :deletion_list, :nullification_list, :deletion_list_with_additional_identifiers
59
62
  attr_reader :ignore_table_deletion_list, :ignore_table_nullification_list
60
63
  attr_reader :query_schema_parser
61
64
  attr_reader :current_klass_name
@@ -65,6 +68,15 @@ module BulkDependencyEraser
65
68
  def initialize query:, opts: {}
66
69
  @query = query
67
70
  @deletion_list = {}
71
+ # CTID column values are stored here
72
+ # - key: <klass_name (with or without suffixes)>
73
+ # - value: Hash
74
+ # - key: Individual record's ID
75
+ # - value: Hash
76
+ # - key: Column Name
77
+ # - value: plucked column value
78
+ # - these additional identifiers will be used when deleting the klasses
79
+ @deletion_list_with_additional_identifiers = {}
68
80
  @nullification_list = {}
69
81
 
70
82
  # For any ignored table results, they will be stored here
@@ -95,9 +107,13 @@ module BulkDependencyEraser
95
107
  # - prior approach was to use table_name.classify, but we can't trust that approach.
96
108
  opts_c.ignore_tables.each do |table_name|
97
109
  table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
98
- klass_index = deletion_index_key(klass_name.constantize)
99
- ignore_table_deletion_list[klass_index] = deletion_list.delete(klass_index) if deletion_list.key?(klass_index)
100
- ignore_table_nullification_list[klass_index] = nullification_list.delete(klass_index) if nullification_list.key?(klass_index)
110
+ klass = klass_name.constantize
111
+ # Delete klasses from the deletion/nullification lists if they are from ignored tables.
112
+ # - Handles klass name indexes, with and without suffices.
113
+ deletion_index_keys(klass).each do |klass_index|
114
+ ignore_table_deletion_list[klass_index] = deletion_list.delete(klass_index) if deletion_list.key?(klass_index)
115
+ ignore_table_nullification_list[klass_index] = nullification_list.delete(klass_index) if nullification_list.key?(klass_index)
116
+ end
101
117
  end
102
118
  end
103
119
 
@@ -137,27 +153,44 @@ module BulkDependencyEraser
137
153
  end
138
154
  end
139
155
 
140
- def pluck_from_query(query, column = :id)
156
+ # @param [ActiveRecord::Relation] query
157
+ # @param [Symbol | Array<Symbol>] column - primary_key must be the first or only element in the column.
158
+ # @return [
159
+ # [Array<String | Int>] array of column values,
160
+ # [Hash] hash of column/value pairings - additional identifiers.
161
+ # ]
162
+ def pluck_from_query(query, column = :id, skip_ctid_check: false)
163
+ if column.is_a?(Array)
164
+ columns = column
165
+ else
166
+ columns = [column]
167
+ end
168
+
169
+ # Only pluck CTID if we're plucking column :id (the only primary key supported)
170
+ if columns == [:id] && opts_c.use_pg_system_column_ctid && skip_ctid_check == false
171
+ columns << :ctid
172
+ end
173
+
141
174
  set_current_klass_name(query)
142
175
  # ordering shouldn't matter in these queries, and would slow it down
143
176
  # - we're ignoring default_scope ordering, but assoc-defined ordering would still take effect
144
177
  query = query.reorder('')
145
178
  query = custom_scope_for_query(query)
146
179
 
147
- query_ids = []
180
+ query_results = []
148
181
  read_from_db do
149
182
  # If the query has a limit, then we don't want to clobber with batching.
150
183
  if batching_disabled? || !query.where({}).limit_value.nil?
151
184
  # query without batching
152
- query_ids = query.pluck(column)
185
+ query_results = query.pluck(*columns)
153
186
  elsif opts_c.disable_batch_ordering || opts_c.disable_batch_ordering_for_klasses.include?(current_klass_name)
154
187
  # query with orderless batching
155
188
  offset = 0
156
189
  loop do
157
- new_query_ids = query.offset(offset).limit(batch_size).pluck(column)
158
- query_ids += new_query_ids
190
+ new_query_results = query.offset(offset).limit(batch_size).pluck(*columns)
191
+ query_results += new_query_results
159
192
 
160
- break if new_query_ids.size < batch_size
193
+ break if new_query_results.size < batch_size
161
194
 
162
195
  # Move to the next batch
163
196
  offset += batch_size
@@ -165,12 +198,35 @@ module BulkDependencyEraser
165
198
  else
166
199
  # query with ordered batching
167
200
  query.in_batches(of: batch_size) do |subset_query|
168
- query_ids += subset_query.pluck(column)
201
+ query_results += subset_query.pluck(*columns)
169
202
  end
170
203
  end
171
204
  end
172
205
 
173
- return query_ids
206
+ # Early exit if we only plucked one column.
207
+ # - query_results would just be an array of IDs
208
+ return [query_results, nil] if columns.count == 1
209
+
210
+ transposed_results = query_results.transpose
211
+
212
+ query_primary_keys = transposed_results.shift || [] # could be an empty id set
213
+ columns.shift # shift of the primary key column
214
+
215
+ query_additional_identifiers = {}
216
+ results_per_additional_column = {}
217
+ columns.each do |column|
218
+ column_results = transposed_results.shift
219
+ results_per_additional_column[column] = column_results
220
+ end
221
+
222
+ query_primary_keys.each_with_index do |primary_key, plucked_index|
223
+ query_additional_identifiers[primary_key] ||= {}
224
+ results_per_additional_column.each do |column, column_results|
225
+ query_additional_identifiers[primary_key][column] = column_results[plucked_index]
226
+ end
227
+ end
228
+
229
+ return query_primary_keys, query_additional_identifiers
174
230
  end
175
231
 
176
232
  def batch_size
@@ -181,6 +237,32 @@ module BulkDependencyEraser
181
237
  opts_c.disable_read_batching.nil? ? opts_c.disable_batching : opts_c.disable_read_batching
182
238
  end
183
239
 
240
+ # No need for recursion here. Nullifying does not effect nested dependencies
241
+ def nullification_query_parser(query, nullfication_klass, klass_foreign_key, klass_foreign_type = nil)
242
+ # We're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
243
+ # Setting skip_ctid_check: true because we currently don't support CTID with nullifications yet
244
+ assoc_ids, additional_identifiers_by_id = pluck_from_query(query, skip_ctid_check: true)
245
+ klass_name = nullfication_klass.name
246
+
247
+ # No assoc_ids, no need to add it to the nullification list
248
+ return if assoc_ids.none?
249
+
250
+ klass_foreign_key = klass_foreign_key.to_s
251
+ nullification_list[klass_name] ||= {}
252
+ nullification_list[klass_name][klass_foreign_key] ||= []
253
+ nullification_list[klass_name][klass_foreign_key] += assoc_ids
254
+ nullification_list[klass_name][klass_foreign_key].uniq!
255
+
256
+
257
+ # Also nullify the 'type' field, if the association is polymorphic
258
+ if klass_foreign_type
259
+ klass_foreign_type = klass_foreign_type.to_s
260
+ nullification_list[klass_name][klass_foreign_type] ||= []
261
+ nullification_list[klass_name][klass_foreign_type] += assoc_ids
262
+ nullification_list[klass_name][klass_foreign_type].uniq!
263
+ end
264
+ end
265
+
184
266
  def deletion_query_parser query, association_parent = nil
185
267
  # necessary for "ActiveRecord::Reflection::ThroughReflection" use-case
186
268
  # force_through_destroy_chains = options[:force_destroy_chain] || {}
@@ -225,24 +307,28 @@ module BulkDependencyEraser
225
307
  end
226
308
 
227
309
  # Pluck IDs of the current query
228
- query_ids = pluck_from_query(query)
310
+ query_ids, additional_identifiers_by_id = pluck_from_query(query)
229
311
 
230
312
  klass_index = initialize_deletion_list_for_klass(klass)
231
313
 
232
314
  # prevent infinite recursion here.
233
315
  # - Remove any IDs that have been processed before
234
- query_ids = remove_already_deletion_processed_ids(klass, query_ids)
316
+ query_ids, additional_identifiers_by_id = remove_already_deletion_processed_ids(klass, query_ids, additional_identifiers_by_id:)
235
317
 
236
318
  # If ids are nil, let's find that error
237
319
  if query_ids.none? #|| query_ids.nil?
238
320
  # quick cleanup, if turns out was an empty class
239
- deletion_list.delete(klass_index) if deletion_list[klass_index].none?
321
+ if deletion_list[klass_index].none?
322
+ deletion_list.delete(klass_index)
323
+ deletion_list_with_additional_identifiers.delete(klass_index)
324
+ end
240
325
  return
241
326
  end
242
327
 
243
328
  # Use-case: We have more IDs to process
244
329
  # - can now safely add to the list, since we've prevented infinite recursion
245
330
  deletion_list[klass_index] += query_ids
331
+ deletion_list_with_additional_identifiers[klass_index].merge!(additional_identifiers_by_id)
246
332
 
247
333
  # ignore associations that aren't a dependent destroyable type
248
334
  destroy_associations = query.reflect_on_all_associations.select do |reflection|
@@ -378,25 +464,25 @@ module BulkDependencyEraser
378
464
  end
379
465
 
380
466
  # Look for manually specified keys in the assocation first
381
- specified_primary_key = reflection.options[:primary_key]&.to_s
382
- specified_foreign_key = reflection.options[:foreign_key]&.to_s
467
+ specified_primary_key = reflection.options[:primary_key]&.to_sym
468
+ specified_foreign_key = reflection.options[:foreign_key]&.to_sym
383
469
  # For polymorphic_associations
384
470
  specified_foreign_type = nil
385
471
 
386
472
  # handle foreign_key edge cases
387
473
  if specified_foreign_key.nil?
388
474
  if reflection.options[:as]
389
- specified_foreign_type = "#{reflection.options[:as]}_type"
390
- specified_foreign_key = "#{reflection.options[:as]}_id"
475
+ specified_foreign_type = "#{reflection.options[:as]}_type".to_sym
476
+ specified_foreign_key = "#{reflection.options[:as]}_id".to_sym
391
477
  # Only filtering by type here, the extra work for a poly assoc. We filter by IDs later
392
- assoc_query = assoc_query.where({ specified_foreign_type.to_sym => parent_class.name })
478
+ assoc_query = assoc_query.where({ specified_foreign_type => parent_class.name })
393
479
  else
394
- specified_foreign_key = parent_class.table_name.singularize + "_id"
480
+ specified_foreign_key = "#{parent_class.table_name.singularize}_id".to_sym
395
481
  end
396
482
  end
397
483
 
398
484
  # Check to see if foreign_key exists in association class's table
399
- unless assoc_klass.column_names.include?(specified_foreign_key)
485
+ unless assoc_klass.column_names.include?(specified_foreign_key.to_s)
400
486
  report_error(
401
487
  "
402
488
  For #{parent_class.name}'s assoc '#{assoc_klass.name}': Could not determine the assoc's foreign key.
@@ -409,11 +495,11 @@ module BulkDependencyEraser
409
495
  # Build association query, based on parent class's primary key and the assoc's foreign key
410
496
  # - handle primary key edge cases
411
497
  # - The associations might not be using the primary_key of the klass table, but we can support that here.
412
- if specified_primary_key && specified_primary_key&.to_s != 'id'
413
- alt_primary_ids = pluck_from_query(query, specified_primary_key)
414
- assoc_query = assoc_query.where(specified_foreign_key.to_sym => alt_primary_ids)
498
+ if specified_primary_key && specified_primary_key != :id
499
+ alt_primary_ids, _ = pluck_from_query(query, specified_primary_key)
500
+ assoc_query = assoc_query.where(specified_foreign_key => alt_primary_ids)
415
501
  else
416
- assoc_query = assoc_query.where(specified_foreign_key.to_sym => query_ids)
502
+ assoc_query = assoc_query.where(specified_foreign_key => query_ids)
417
503
  end
418
504
 
419
505
  # remove any ordering or limits imposed on the association queries from the association definitions
@@ -427,25 +513,7 @@ module BulkDependencyEraser
427
513
  deletion_query_parser(assoc_query, parent_class)
428
514
  end
429
515
  elsif type == :nullify
430
- # No need for recursion here.
431
- # - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
432
- assoc_ids = pluck_from_query(assoc_query)
433
-
434
- # No assoc_ids, no need to add it to the nullification list
435
- return if assoc_ids.none?
436
-
437
- nullification_list[assoc_klass_name] ||= {}
438
- nullification_list[assoc_klass_name][specified_foreign_key] ||= []
439
- nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
440
- nullification_list[assoc_klass_name][specified_foreign_key].uniq!
441
-
442
-
443
- # Also nullify the 'type' field, if the association is polymorphic
444
- if specified_foreign_type
445
- nullification_list[assoc_klass_name][specified_foreign_type] ||= []
446
- nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
447
- nullification_list[assoc_klass_name][specified_foreign_type].uniq!
448
- end
516
+ nullification_query_parser(assoc_query, assoc_klass, specified_foreign_key, specified_foreign_type)
449
517
  else
450
518
  raise "invalid parsing type: #{type}"
451
519
  end
@@ -531,7 +599,7 @@ module BulkDependencyEraser
531
599
  return
532
600
  end
533
601
 
534
- foreign_keys = pluck_from_query(query, specified_foreign_key)
602
+ foreign_keys, _ = pluck_from_query(query, specified_foreign_key, skip_ctid_check: true)
535
603
  assoc_query = assoc_query.where(
536
604
  specified_primary_key.to_sym => foreign_keys
537
605
  )
@@ -690,8 +758,14 @@ module BulkDependencyEraser
690
758
  opts_c.db_read_wrapper.call(block)
691
759
  end
692
760
 
693
- def remove_already_deletion_processed_ids(klass, new_ids)
761
+ # @return [
762
+ # [Array] array of IDs that haven't already been added to the deletion list
763
+ # [Hash | Nil] the additional deletion query identifiers, minus the ones already processed
764
+ # - will be nil if we're not plucking additional columns.
765
+ # ]
766
+ def remove_already_deletion_processed_ids(klass, new_ids, additional_identifiers_by_id: nil)
694
767
  already_processed_ids = []
768
+ additional_identifiers_by_id ||= {}
695
769
 
696
770
  if is_a_circular_dependency_klass?(klass)
697
771
  klass_keys = find_circular_dependency_deletion_keys(klass)
@@ -702,7 +776,10 @@ module BulkDependencyEraser
702
776
  already_processed_ids = deletion_list[klass.name]
703
777
  end
704
778
 
705
- new_ids - already_processed_ids
779
+ return [
780
+ (new_ids - already_processed_ids),
781
+ additional_identifiers_by_id.except(*already_processed_ids)
782
+ ]
706
783
  end
707
784
 
708
785
  # Initializes deletion_list index
@@ -714,9 +791,11 @@ module BulkDependencyEraser
714
791
  raise "circular_index already existed for klass: #{klass.name}" if deletion_list.key?(klass_index)
715
792
 
716
793
  deletion_list[klass_index] = []
794
+ deletion_list_with_additional_identifiers[klass_index] = {}
717
795
  else
718
796
  # Not a circular dependency, define as normal
719
797
  deletion_list[klass_index] ||= []
798
+ deletion_list_with_additional_identifiers[klass_index] ||= {}
720
799
  end
721
800
 
722
801
  klass_index
@@ -734,6 +813,14 @@ module BulkDependencyEraser
734
813
  deletion_list.keys.select { |key| key.match?(regex) }
735
814
  end
736
815
 
816
+ # returns any current index keys, with or without suffix, in the deletion_list for the given klass param
817
+ def deletion_index_keys(klass)
818
+ klass_keys = []
819
+ klass_keys << klass.name if deletion_list.keys.include?(klass.name)
820
+ klass_keys += find_circular_dependency_deletion_keys(klass)
821
+ return klass_keys
822
+ end
823
+
737
824
  # If circular dependency, append a index suffix to the deletion hash key
738
825
  # - they will be deleted in highest index to lowest index order.
739
826
  def deletion_index_key(klass, increment_circular_index: false)
@@ -53,8 +53,9 @@ module BulkDependencyEraser
53
53
  deletion_proc_scopes_per_class_name: {},
54
54
  }.freeze
55
55
 
56
- def initialize class_names_and_ids: {}, opts: {}
56
+ def initialize class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}
57
57
  @class_names_and_ids = class_names_and_ids
58
+ @additional_identifiers_by_id = additional_identifiers_by_id
58
59
  super(opts:)
59
60
  end
60
61
 
@@ -68,19 +69,23 @@ module BulkDependencyEraser
68
69
  begin
69
70
  class_names_and_ids.keys.reverse.each do |class_name|
70
71
  current_class_name = class_name
72
+ additional_identifiers = additional_identifiers_by_id[class_name]
73
+ raise "invalid state! #{class_name} not found in 'additional_identifiers_by_id' list" if additional_identifiers.nil?
74
+
75
+ # Last in, First out
71
76
  ids = class_names_and_ids[class_name].reverse
72
77
  klass = constantize(class_name)
73
78
 
74
79
  if opts_c.enable_invalid_foreign_key_detection
75
80
  # delete with referential integrity
76
- delete_by_klass_and_ids(klass, ids)
81
+ delete_by_klass_and_ids(klass, ids, additional_identifiers:)
77
82
  else
78
83
  # delete without referential integrity
79
84
  # Disable any ActiveRecord::InvalidForeignKey raised errors.
80
85
  # - src: https://stackoverflow.com/questions/41005849/rails-migrations-temporarily-ignore-foreign-key-constraint
81
86
  # https://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractAdapter/disable_referential_integrity
82
87
  ActiveRecord::Base.connection.disable_referential_integrity do
83
- delete_by_klass_and_ids(klass, ids)
88
+ delete_by_klass_and_ids(klass, ids, additional_identifiers:)
84
89
  end
85
90
  end
86
91
  end
@@ -94,7 +99,7 @@ module BulkDependencyEraser
94
99
 
95
100
  protected
96
101
 
97
- attr_reader :class_names_and_ids
102
+ attr_reader :class_names_and_ids, :additional_identifiers_by_id
98
103
 
99
104
  def custom_scope_for_query(query)
100
105
  klass = query.klass
@@ -113,29 +118,49 @@ module BulkDependencyEraser
113
118
  opts_c.disable_delete_batching.nil? ? opts_c.disable_batching : opts_c.disable_delete_batching
114
119
  end
115
120
 
116
- def delete_by_klass_and_ids klass, ids
121
+ def delete_by_klass_and_ids(klass, ids, additional_identifiers:)
117
122
  puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
118
123
  query = klass.unscoped
119
124
  query = custom_scope_for_query(query)
120
125
 
121
126
  if batching_disabled?
122
127
  puts "Deleting without batching" if opts_c.verbose
128
+ # Get column-names/keys of any additional identifer columns
129
+ detected_additional_identifier_columns = additional_identifiers.values.flat_map(&:keys).uniq
123
130
  delete_in_db do
124
- deletion_result = query.where(id: ids).delete_all
131
+ deletion_query = query.where(id: ids)
132
+ # Apply any additional query identifiers (i.e. :ctid column)
133
+ detected_additional_identifier_columns.each do |column|
134
+ deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
135
+ end
136
+
137
+ # Perform Deletion
138
+ deletion_result = deletion_query.delete_all
125
139
  # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
126
140
  # and have access to these objects in their proc.
127
141
  # - query can give them access to the klass and table_name
128
- [deletion_result, query, ids]
142
+ [deletion_result, query, ids, additional_identifiers]
129
143
  end
130
144
  else
131
145
  puts "Deleting with batching" if opts_c.verbose
132
146
  ids.each_slice(batch_size) do |ids_subset|
147
+ additional_identifiers_subset = additional_identifiers.slice(*ids_subset)
148
+ # Get column-names/keys of any additional identifer columns
149
+ detected_additional_identifier_columns = additional_identifiers_subset.values.flat_map(&:keys).uniq
133
150
  delete_in_db do
134
- deletion_result = query.where(id: ids_subset).delete_all
151
+ deletion_query = query.where(id: ids_subset)
152
+ # Apply any additional query identifiers (i.e. :ctid column)
153
+ detected_additional_identifier_columns.each do |column|
154
+ deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
155
+ end
156
+
157
+ # Perform Deletion
158
+ deletion_result = deletion_query.delete_all
159
+
135
160
  # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
136
161
  # and have access to these objects in their proc.
137
162
  # - query can give them access to the klass and table_name
138
- [deletion_result, query, ids_subset]
163
+ [deletion_result, query, ids_subset, additional_identifiers_subset]
139
164
  end
140
165
  end
141
166
  end
@@ -7,7 +7,7 @@ module BulkDependencyEraser
7
7
  verbose: false,
8
8
  }.freeze
9
9
 
10
- delegate :nullification_list, :deletion_list, to: :dependency_builder
10
+ delegate :nullification_list, :deletion_list, :deletion_list_with_additional_identifiers, to: :dependency_builder
11
11
  delegate :ignore_table_deletion_list, :ignore_table_nullification_list, to: :dependency_builder
12
12
  delegate :circular_dependency_klasses, :flat_dependencies_per_klass, to: :dependency_builder
13
13
 
@@ -49,7 +49,11 @@ module BulkDependencyEraser
49
49
  end
50
50
 
51
51
  def delete!
52
- @deleter = BulkDependencyEraser::Deleter.new(class_names_and_ids: deletion_list, opts:)
52
+ @deleter = BulkDependencyEraser::Deleter.new(
53
+ class_names_and_ids: deletion_list,
54
+ additional_identifiers_by_id: deletion_list_with_additional_identifiers,
55
+ opts:
56
+ )
53
57
  deleter_execution = deleter.execute
54
58
  unless deleter_execution
55
59
  puts "Deleter execution FAILED" if opts_c.verbose
@@ -1,3 +1,3 @@
1
1
  module BulkDependencyEraser
2
- VERSION = "4.0.0".freeze
2
+ VERSION = "4.1.0".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: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin.dana.software.dev@gmail.com