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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e51d0383cb5b2111181c9dc48b499b046d225adad1d1f8b985216a0d395cb612
4
- data.tar.gz: 02ce5200f03d7a4cec2cfa72b64fbfdf84e73ce5b3e6b43ba073173b48e5139b
3
+ metadata.gz: 3cafe34b586a54a169d2d728ba0a39d9403c90796db9f40c9050620eaa4ef156
4
+ data.tar.gz: a1a7001243a75db7740e2fbcbdfbf43dad5253f83de8c757bb7b2d0ab2f57763
5
5
  SHA512:
6
- metadata.gz: 25857421c4e8cc6af0f952bacd106b39f19d34ac8d2292f74e9daa90c10ea07ab82e11fe584b7c0fcb60209fb1e4ba0d126e6e9102587b3d6ff6a69e495373e5
7
- data.tar.gz: 68f2ec5423d953bf842de7c98ea778f44795a89cfa948b400c5171e08489446aba765467744175a5de69df6957fef1b04f0c017af7d63b1959d756caaac07a22
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
- build_result = deletion_query_parser(@query)
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
- table_klass_name = query.table_name.classify
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[table_klass_name] ||= []
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[table_klass_name]
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(table_klass_name) if deletion_list[table_klass_name].none?
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[table_klass_name] += query_ids
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[table_klass_name].sort! if Rails.env.test?
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
- assoc_table_klass_name = assoc_klass.table_name.classify
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
- # puts "FOUND IDS: #{assoc_ids.insect}"
259
- nullification_list[assoc_table_klass_name] ||= {}
260
- nullification_list[assoc_table_klass_name][specified_foreign_key] ||= []
261
- nullification_list[assoc_table_klass_name][specified_foreign_key] += assoc_ids
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
- # Disable any ActiveRecord::InvalidForeignKey raised errors.
23
- # src https://stackoverflow.com/questions/41005849/rails-migrations-temporarily-ignore-foreign-key-constraint
24
- # https://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractAdapter/disable_referential_integrity
25
- ActiveRecord::Base.connection.disable_referential_integrity do
26
- delete_in_db do
27
- klass.unscoped.where(id: ids).delete_all
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
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: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin.dana.software.dev@gmail.com