bulk_dependency_eraser 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bulk_dependency_eraser/base.rb +68 -19
- data/lib/bulk_dependency_eraser/builder.rb +89 -61
- data/lib/bulk_dependency_eraser/deleter.rb +3 -3
- data/lib/bulk_dependency_eraser/full_schema_parser.rb +179 -0
- data/lib/bulk_dependency_eraser/manager.rb +1 -0
- data/lib/bulk_dependency_eraser/nullifier.rb +3 -3
- data/lib/bulk_dependency_eraser/query_schema_parser.rb +281 -0
- data/lib/bulk_dependency_eraser/utils.rb +25 -0
- data/lib/bulk_dependency_eraser/version.rb +1 -1
- data/lib/bulk_dependency_eraser.rb +3 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73ac2c6209f874175a8a1208c184a3fdaa73f2020a411f2f41bd069668b63fbc
|
4
|
+
data.tar.gz: 86df357905f75f34337d30446cfc6b34b9aa55fc1947d17a008329379f2beae7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69fd26f7fcbd04f158adf4bc038181818136f17d878bad544bb888b1d0f547d557153204876a91b222154f7154d0f38b143f4db08f5081242c107488832fa682
|
7
|
+
data.tar.gz: b31893d532b1be190f5288ddfb495008615a3c54f2e5a9fb12344a733c4322345911597822c835277f9f4632657aeefe4ee7a77fe7eecea16cf64151dd63a797
|
@@ -1,5 +1,11 @@
|
|
1
|
+
require_relative 'utils'
|
2
|
+
|
1
3
|
module BulkDependencyEraser
|
2
4
|
class Base
|
5
|
+
POLY_KLASS_NAME = "<POLY>"
|
6
|
+
include BulkDependencyEraser::Utils
|
7
|
+
extend BulkDependencyEraser::Utils
|
8
|
+
|
3
9
|
# Default Custom Scope for all classes, no effect.
|
4
10
|
DEFAULT_SCOPE_WRAPPER = ->(query) { nil }
|
5
11
|
# Default Custom Scope for mapped-by-name classes, no effect.
|
@@ -39,6 +45,32 @@ module BulkDependencyEraser
|
|
39
45
|
proc_scopes_per_class_name: {},
|
40
46
|
}.freeze
|
41
47
|
|
48
|
+
DEPENDENCY_NULLIFY = %i[
|
49
|
+
nullify
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
# Abort deletion if assoc dependency value is any of these.
|
53
|
+
# - exception if the :force_destroy_restricted option set true
|
54
|
+
DEPENDENCY_RESTRICT = %i[
|
55
|
+
restrict_with_error
|
56
|
+
restrict_with_exception
|
57
|
+
].freeze
|
58
|
+
|
59
|
+
DEPENDENCY_DESTROY = (
|
60
|
+
%i[
|
61
|
+
destroy
|
62
|
+
delete_all
|
63
|
+
destroy_async
|
64
|
+
] + self::DEPENDENCY_RESTRICT
|
65
|
+
).freeze
|
66
|
+
|
67
|
+
DEPENDENCY_DESTROY_IGNORE_REFLECTION_TYPES = [
|
68
|
+
# Rails 6.1, when a has_and_delongs_to_many <assoc>, dependent: :destroy,
|
69
|
+
# will ignore the destroy. Will neither destroy the join table record nor the association record
|
70
|
+
# We will do the same, mirror the fuctionality, by ignoring any :dependent options on these types.
|
71
|
+
'ActiveRecord::Reflection::HasAndBelongsToManyReflection'
|
72
|
+
].freeze
|
73
|
+
|
42
74
|
attr_reader :errors
|
43
75
|
|
44
76
|
def initialize opts: {}
|
@@ -53,8 +85,44 @@ module BulkDependencyEraser
|
|
53
85
|
raise NotImplementedError
|
54
86
|
end
|
55
87
|
|
88
|
+
def report_error msg
|
89
|
+
# remove new lines, surrounding white space, replace with semicolon delimiters
|
90
|
+
n = msg.strip.gsub(/\s*\n\s*/, ' ')
|
91
|
+
@errors << n
|
92
|
+
end
|
93
|
+
|
94
|
+
def merge_errors errors, prefix = nil
|
95
|
+
local_errors = errors.dup
|
96
|
+
|
97
|
+
unless local_errors.any?
|
98
|
+
local_errors << '<NO ERRORS FOUND TO MERGE>'
|
99
|
+
end
|
100
|
+
|
101
|
+
if prefix
|
102
|
+
local_errors = errors.map { |error| prefix + error }
|
103
|
+
end
|
104
|
+
@errors += local_errors
|
105
|
+
end
|
106
|
+
|
56
107
|
protected
|
57
108
|
|
109
|
+
def constantize(klass_name)
|
110
|
+
# circular dependencies have suffixes, shave them off
|
111
|
+
klass_name.sub(/\.\d+$/, '').constantize
|
112
|
+
end
|
113
|
+
|
114
|
+
# A dependent assoc may be through another association. Follow the throughs to find the correct assoc to destroy.
|
115
|
+
# @return [Symbol] - association's name
|
116
|
+
def find_root_association_from_through_assocs klass, association_name
|
117
|
+
reflection = klass.reflect_on_association(association_name)
|
118
|
+
options = reflection.options
|
119
|
+
if options.key?(:through)
|
120
|
+
return find_root_association_from_through_assocs(klass, options[:through])
|
121
|
+
else
|
122
|
+
association_name
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
58
126
|
def custom_scope_for_query(query)
|
59
127
|
klass = query.klass
|
60
128
|
if opts_c.proc_scopes_per_class_name.key?(klass.name)
|
@@ -79,25 +147,6 @@ module BulkDependencyEraser
|
|
79
147
|
).freeze
|
80
148
|
end
|
81
149
|
|
82
|
-
def report_error msg
|
83
|
-
# remove new lines, surrounding white space, replace with semicolon delimiters
|
84
|
-
n = msg.strip.gsub(/\s*\n\s*/, ' ')
|
85
|
-
@errors << n
|
86
|
-
end
|
87
|
-
|
88
|
-
def merge_errors errors, prefix = nil
|
89
|
-
local_errors = errors.dup
|
90
|
-
|
91
|
-
unless local_errors.any?
|
92
|
-
local_errors << '<NO ERRORS FOUND TO MERGE>'
|
93
|
-
end
|
94
|
-
|
95
|
-
if prefix
|
96
|
-
local_errors = errors.map { |error| prefix + error }
|
97
|
-
end
|
98
|
-
@errors += local_errors
|
99
|
-
end
|
100
|
-
|
101
150
|
def uniqify_errors!
|
102
151
|
@errors.uniq!
|
103
152
|
end
|
@@ -34,35 +34,12 @@ module BulkDependencyEraser
|
|
34
34
|
reading_proc_scopes_per_class_name: {},
|
35
35
|
}.freeze
|
36
36
|
|
37
|
-
DEPENDENCY_NULLIFY = %i[
|
38
|
-
nullify
|
39
|
-
].freeze
|
40
|
-
|
41
|
-
# Abort deletion if assoc dependency value is any of these.
|
42
|
-
# - exception if the :force_destroy_restricted option set true
|
43
|
-
DEPENDENCY_RESTRICT = %i[
|
44
|
-
restrict_with_error
|
45
|
-
restrict_with_exception
|
46
|
-
].freeze
|
47
|
-
|
48
|
-
DEPENDENCY_DESTROY = (
|
49
|
-
%i[
|
50
|
-
destroy
|
51
|
-
delete_all
|
52
|
-
destroy_async
|
53
|
-
] + self::DEPENDENCY_RESTRICT
|
54
|
-
).freeze
|
55
|
-
|
56
|
-
DEPENDENCY_DESTROY_IGNORE_REFLECTION_TYPES = [
|
57
|
-
# Rails 6.1, when a has_and_delongs_to_many <assoc>, dependent: :destroy,
|
58
|
-
# will ignore the destroy. Will neither destroy the join table record nor the association record
|
59
|
-
# We will do the same, mirror the fuctionality, by ignoring any :dependent options on these types.
|
60
|
-
'ActiveRecord::Reflection::HasAndBelongsToManyReflection'
|
61
|
-
].freeze
|
62
|
-
|
63
37
|
# write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.
|
64
38
|
attr_accessor :deletion_list, :nullification_list
|
65
39
|
attr_reader :ignore_table_deletion_list, :ignore_table_nullification_list
|
40
|
+
attr_reader :query_schema_parser
|
41
|
+
|
42
|
+
delegate :circular_dependency_klasses, :flat_dependencies_per_klass, to: :query_schema_parser
|
66
43
|
|
67
44
|
def initialize query:, opts: {}
|
68
45
|
@query = query
|
@@ -79,9 +56,15 @@ module BulkDependencyEraser
|
|
79
56
|
|
80
57
|
@ignore_table_name_and_dependencies = opts_c.ignore_tables_and_dependencies.collect { |table_name| table_name }
|
81
58
|
@ignore_klass_name_and_dependencies = opts_c.ignore_klass_names_and_dependencies.collect { |klass_name| klass_name }
|
59
|
+
@query_schema_parser = BulkDependencyEraser::QuerySchemaParser.new(query:, opts:)
|
82
60
|
end
|
83
61
|
|
84
62
|
def execute
|
63
|
+
unless query_schema_parser.execute
|
64
|
+
merge_errors(full_schema_parser.errors, 'QuerySchemaParser: ')
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
85
68
|
# go through deletion/nullification lists and remove any tables from 'ignore_tables' option
|
86
69
|
build_result = build
|
87
70
|
|
@@ -89,8 +72,9 @@ module BulkDependencyEraser
|
|
89
72
|
# - prior approach was to use table_name.classify, but we can't trust that approach.
|
90
73
|
opts_c.ignore_tables.each do |table_name|
|
91
74
|
table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
|
92
|
-
|
93
|
-
|
75
|
+
klass_index = deletion_index_key(klass_name.constantize)
|
76
|
+
ignore_table_deletion_list[klass_index] = deletion_list.delete(klass_index) if deletion_list.key?(klass_index)
|
77
|
+
ignore_table_nullification_list[klass_index] = nullification_list.delete(klass_index) if nullification_list.key?(klass_index)
|
94
78
|
end
|
95
79
|
end
|
96
80
|
|
@@ -111,15 +95,13 @@ module BulkDependencyEraser
|
|
111
95
|
rescue StandardError => e
|
112
96
|
if @query.is_a?(ActiveRecord::Relation)
|
113
97
|
klass = @query.klass
|
114
|
-
klass_name = @query.klass.name
|
115
98
|
else
|
116
99
|
# current_query is a normal rails class
|
117
100
|
klass = @query
|
118
|
-
klass_name = @query.name
|
119
101
|
end
|
120
102
|
report_error(
|
121
103
|
"
|
122
|
-
Error Encountered in 'execute' for '#{
|
104
|
+
Error Encountered in 'execute' for '#{klass.name}':
|
123
105
|
#{e.class.name}
|
124
106
|
#{e.message}
|
125
107
|
"
|
@@ -190,17 +172,15 @@ module BulkDependencyEraser
|
|
190
172
|
|
191
173
|
if query.is_a?(ActiveRecord::Relation)
|
192
174
|
klass = query.klass
|
193
|
-
klass_name = query.klass.name
|
194
175
|
else
|
195
176
|
# current_query is a normal rails class
|
196
177
|
klass = query
|
197
|
-
klass_name = query.name
|
198
178
|
end
|
199
179
|
|
200
180
|
table_names_to_parsed_klass_names[klass.table_name] ||= []
|
201
181
|
# Need to populate this list here, so we can have access to it later for the :ignore_tables option
|
202
|
-
unless table_names_to_parsed_klass_names[klass.table_name].include?(
|
203
|
-
table_names_to_parsed_klass_names[klass.table_name] <<
|
182
|
+
unless table_names_to_parsed_klass_names[klass.table_name].include?(klass.name)
|
183
|
+
table_names_to_parsed_klass_names[klass.table_name] << klass.name
|
204
184
|
end
|
205
185
|
|
206
186
|
if ignore_table_name_and_dependencies.include?(klass.table_name)
|
@@ -208,22 +188,22 @@ module BulkDependencyEraser
|
|
208
188
|
return
|
209
189
|
end
|
210
190
|
|
211
|
-
if ignore_klass_name_and_dependencies.include?(
|
191
|
+
if ignore_klass_name_and_dependencies.include?(klass.name)
|
212
192
|
# Not parsing, table and dependencies ignorable
|
213
193
|
return
|
214
194
|
end
|
215
195
|
|
216
196
|
if opts_c.verbose
|
217
197
|
if association_parent
|
218
|
-
puts "Building #{association_parent}, association of #{
|
198
|
+
puts "Building #{association_parent}, association of #{klass.name}"
|
219
199
|
else
|
220
|
-
puts "Building #{
|
200
|
+
puts "Building #{klass.name}"
|
221
201
|
end
|
222
202
|
end
|
223
203
|
|
224
204
|
if klass.primary_key != 'id'
|
225
205
|
report_error(
|
226
|
-
"#{
|
206
|
+
"#{klass.name} - does not use primary_key 'id'. Cannot use this tool to bulk delete."
|
227
207
|
)
|
228
208
|
return
|
229
209
|
end
|
@@ -231,26 +211,22 @@ module BulkDependencyEraser
|
|
231
211
|
# Pluck IDs of the current query
|
232
212
|
query_ids = pluck_from_query(query)
|
233
213
|
|
234
|
-
|
214
|
+
klass_index = initialize_deletion_list_for_klass(klass)
|
235
215
|
|
236
216
|
# prevent infinite recursion here.
|
237
217
|
# - Remove any IDs that have been processed before
|
238
|
-
query_ids = query_ids
|
218
|
+
query_ids = remove_already_deletion_processed_ids(klass, query_ids)
|
239
219
|
|
240
220
|
# If ids are nil, let's find that error
|
241
221
|
if query_ids.none? #|| query_ids.nil?
|
242
222
|
# quick cleanup, if turns out was an empty class
|
243
|
-
deletion_list.delete(
|
223
|
+
deletion_list.delete(klass_index) if deletion_list[klass_index].none?
|
244
224
|
return
|
245
225
|
end
|
246
226
|
|
247
227
|
# Use-case: We have more IDs to process
|
248
228
|
# - can now safely add to the list, since we've prevented infinite recursion
|
249
|
-
deletion_list[
|
250
|
-
|
251
|
-
# Hard to test if not sorted
|
252
|
-
# - if we had more advanced rspec matches, we could do away with this.
|
253
|
-
# deletion_list[klass_name].sort! if Rails.env.test?
|
229
|
+
deletion_list[klass_index] += query_ids
|
254
230
|
|
255
231
|
# ignore associations that aren't a dependent destroyable type
|
256
232
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
@@ -446,15 +422,12 @@ module BulkDependencyEraser
|
|
446
422
|
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
447
423
|
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
448
424
|
|
449
|
-
# nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
|
450
425
|
|
451
426
|
# Also nullify the 'type' field, if the association is polymorphic
|
452
427
|
if specified_foreign_type
|
453
428
|
nullification_list[assoc_klass_name][specified_foreign_type] ||= []
|
454
429
|
nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
|
455
430
|
nullification_list[assoc_klass_name][specified_foreign_type].uniq!
|
456
|
-
|
457
|
-
# nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
|
458
431
|
end
|
459
432
|
else
|
460
433
|
raise "invalid parsing type: #{type}"
|
@@ -659,17 +632,6 @@ module BulkDependencyEraser
|
|
659
632
|
end
|
660
633
|
end
|
661
634
|
|
662
|
-
# A dependent assoc may be through another association. Follow the throughs to find the correct assoc to destroy.
|
663
|
-
def find_root_association_from_through_assocs klass, association_name
|
664
|
-
reflection = klass.reflect_on_association(association_name)
|
665
|
-
options = reflection.options
|
666
|
-
if options.key?(:through)
|
667
|
-
return find_root_association_from_through_assocs(klass, options[:through])
|
668
|
-
else
|
669
|
-
association_name
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
635
|
# return [Boolean]
|
674
636
|
# - true if valid
|
675
637
|
# - false if not valid
|
@@ -711,5 +673,71 @@ module BulkDependencyEraser
|
|
711
673
|
puts "Reading from DB..." if opts_c.verbose
|
712
674
|
opts_c.db_read_wrapper.call(block)
|
713
675
|
end
|
676
|
+
|
677
|
+
def remove_already_deletion_processed_ids(klass, new_ids)
|
678
|
+
already_processed_ids = []
|
679
|
+
|
680
|
+
if is_a_circular_dependency_klass?(klass)
|
681
|
+
klass_keys = find_circular_dependency_deletion_keys(klass)
|
682
|
+
klass_keys.each do |circular_class_key|
|
683
|
+
already_processed_ids += deletion_list[circular_class_key]
|
684
|
+
end
|
685
|
+
else
|
686
|
+
already_processed_ids = deletion_list[klass.name]
|
687
|
+
end
|
688
|
+
|
689
|
+
new_ids - already_processed_ids
|
690
|
+
end
|
691
|
+
|
692
|
+
# Initializes deletion_list index
|
693
|
+
# - increments the index for circular dependencies
|
694
|
+
def initialize_deletion_list_for_klass(klass)
|
695
|
+
klass_index = deletion_index_key(klass, increment_circular_index: true)
|
696
|
+
|
697
|
+
if is_a_circular_dependency_klass?(klass)
|
698
|
+
raise "circular_index already existed for klass: #{klass.name}" if deletion_list.key?(klass_index)
|
699
|
+
|
700
|
+
deletion_list[klass_index] = []
|
701
|
+
else
|
702
|
+
# Not a circular dependency, define as normal
|
703
|
+
deletion_list[klass_index] ||= []
|
704
|
+
end
|
705
|
+
|
706
|
+
klass_index
|
707
|
+
end
|
708
|
+
|
709
|
+
def is_a_circular_dependency_klass?(klass)
|
710
|
+
circular_dependency_klasses.values.flatten.include?(klass.name)
|
711
|
+
end
|
712
|
+
|
713
|
+
def find_circular_dependency_deletion_keys(klass)
|
714
|
+
escaped_prefix = Regexp.escape(klass.name)
|
715
|
+
# Define the key-matching regex: klass.name + '.' + <integer>
|
716
|
+
regex = /^#{escaped_prefix}\.\d+$/
|
717
|
+
# find the latest, indexed klass initialization
|
718
|
+
deletion_list.keys.select { |key| key.match?(regex) }
|
719
|
+
end
|
720
|
+
|
721
|
+
# If circular dependency, append a index suffix to the deletion hash key
|
722
|
+
# - they will be deleted in highest index to lowest index order.
|
723
|
+
def deletion_index_key(klass, increment_circular_index: false)
|
724
|
+
if is_a_circular_dependency_klass?(klass)
|
725
|
+
klass_keys = find_circular_dependency_deletion_keys(klass)
|
726
|
+
if klass_keys.none?
|
727
|
+
klass_index = "#{klass.name}.0" # first one, no need to consider increment
|
728
|
+
else
|
729
|
+
# Use map to extract the integers using a regular expression
|
730
|
+
circular_indexes = klass_keys.map { |s| s[/\.(\d+)$/, 1].to_i }
|
731
|
+
# Find the maximum value
|
732
|
+
current_circular_index = circular_indexes.max
|
733
|
+
current_circular_index += 1 if increment_circular_index
|
734
|
+
klass_index = "#{klass.name}.#{current_circular_index}"
|
735
|
+
end
|
736
|
+
else
|
737
|
+
klass_index = klass.name
|
738
|
+
end
|
739
|
+
|
740
|
+
return klass_index
|
741
|
+
end
|
714
742
|
end
|
715
743
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module BulkDependencyEraser
|
2
2
|
class Deleter < Base
|
3
|
-
DEFAULT_DB_DELETE_ALL_WRAPPER = ->(block) do
|
3
|
+
DEFAULT_DB_DELETE_ALL_WRAPPER = ->(deleter, block) do
|
4
4
|
begin
|
5
5
|
block.call
|
6
6
|
rescue StandardError => e
|
@@ -63,7 +63,7 @@ module BulkDependencyEraser
|
|
63
63
|
class_names_and_ids.keys.reverse.each do |class_name|
|
64
64
|
current_class_name = class_name
|
65
65
|
ids = class_names_and_ids[class_name].reverse
|
66
|
-
klass = class_name
|
66
|
+
klass = constantize(class_name)
|
67
67
|
|
68
68
|
if opts_c.enable_invalid_foreign_key_detection
|
69
69
|
# delete with referential integrity
|
@@ -140,7 +140,7 @@ module BulkDependencyEraser
|
|
140
140
|
|
141
141
|
def delete_all_in_db(&block)
|
142
142
|
puts "Deleting all from DB..." if opts_c.verbose
|
143
|
-
opts_c.db_delete_all_wrapper.call(block)
|
143
|
+
opts_c.db_delete_all_wrapper.call(self, block)
|
144
144
|
puts "Deleting all from DB complete." if opts_c.verbose
|
145
145
|
end
|
146
146
|
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module BulkDependencyEraser
|
2
|
+
# Create a flat map hash for each class that lists every dependency.
|
3
|
+
class FullSchemaParser < Base
|
4
|
+
DEFAULT_OPTS = {
|
5
|
+
verbose: false,
|
6
|
+
}
|
7
|
+
|
8
|
+
attr_reader :flat_dependencies_per_klass
|
9
|
+
|
10
|
+
@cached_flat_dependencies_per_klass = nil
|
11
|
+
def self.reset_cache
|
12
|
+
@cached_flat_dependencies_per_klass = nil
|
13
|
+
end
|
14
|
+
def self.set_cache(value)
|
15
|
+
@cached_flat_dependencies_per_klass = value.freeze
|
16
|
+
end
|
17
|
+
def self.get_cache
|
18
|
+
@cached_flat_dependencies_per_klass
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(opts: {})
|
22
|
+
# @flat_dependencies_per_klass Structure
|
23
|
+
# {
|
24
|
+
# <class_name> => {
|
25
|
+
# has_dependencies: <Boolean>,
|
26
|
+
# foreign_keys: {
|
27
|
+
# <column_name>: <association_class_name>,
|
28
|
+
# ...
|
29
|
+
# },
|
30
|
+
# nullify_dependencies: {
|
31
|
+
# <association_name>: <association_class_name>,
|
32
|
+
# ...
|
33
|
+
# },
|
34
|
+
# destroy_dependencies: {
|
35
|
+
# <association_name>: <association_class_name>,
|
36
|
+
# ...
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
# }
|
40
|
+
@flat_dependencies_per_klass = {}
|
41
|
+
super(opts:)
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute
|
45
|
+
unless self.class.get_cache.nil?
|
46
|
+
@flat_dependencies_per_klass = self.class.get_cache
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
Rails.application.eager_load!
|
51
|
+
|
52
|
+
ActiveRecord::Base.descendants.each do |model|
|
53
|
+
begin
|
54
|
+
next if model.abstract_class? # Skip abstract classes like ApplicationRecord
|
55
|
+
next unless model.connection.table_exists?(model.table_name)
|
56
|
+
rescue Exception => e
|
57
|
+
report_error("EXECPTION ON #{model.name}; #{e.class}: #{e.message}")
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
flat_dependencies_parser(model)
|
62
|
+
end
|
63
|
+
|
64
|
+
deep_freeze(@flat_dependencies_per_klass)
|
65
|
+
self.class.set_cache(@flat_dependencies_per_klass)
|
66
|
+
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset
|
71
|
+
@flat_dependencies_per_klass = {}
|
72
|
+
self.class.reset_cache
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
# @param klass [ActiveRecord::Base]
|
78
|
+
# @param dependency_path [Array<ActiveRecord::Base>] - previously parsed klasses
|
79
|
+
def flat_dependencies_parser klass
|
80
|
+
raise "invalid klass: #{klass}" unless klass < ActiveRecord::Base
|
81
|
+
|
82
|
+
if flat_dependencies_per_klass.include?(klass.name)
|
83
|
+
raise "@dependencies_per_klass already contains #{klass.name}"
|
84
|
+
end
|
85
|
+
|
86
|
+
@flat_dependencies_per_klass[klass.name] ||= {
|
87
|
+
nullify_dependencies: {},
|
88
|
+
destroy_dependencies: {},
|
89
|
+
has_many: {},
|
90
|
+
belongs_to: {},
|
91
|
+
}
|
92
|
+
|
93
|
+
# We're including :restricted dependencies
|
94
|
+
destroy_associations = klass.reflect_on_all_associations.select do |reflection|
|
95
|
+
dependency_type = reflection.options&.dig(:dependent)
|
96
|
+
dependency_type.to_sym if dependency_type.is_a?(String)
|
97
|
+
DEPENDENCY_DESTROY.include?(dependency_type)
|
98
|
+
end
|
99
|
+
|
100
|
+
nullify_associations = klass.reflect_on_all_associations.select do |reflection|
|
101
|
+
dependency_type = reflection.options&.dig(:dependent)
|
102
|
+
dependency_type.to_sym if dependency_type.is_a?(String)
|
103
|
+
DEPENDENCY_NULLIFY.include?(dependency_type)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Iterate through the assoc names, if there are any :through assocs, then rename the association
|
107
|
+
# - Rails interpretation of any dependencies of a :through association is to apply it to
|
108
|
+
# the leaf association at the end of the :through chain(s)
|
109
|
+
destroy_association_names = destroy_associations.map(&:name).collect do |assoc_name|
|
110
|
+
find_root_association_from_through_assocs(klass, assoc_name)
|
111
|
+
end
|
112
|
+
nullify_association_names = nullify_associations.map(&:name).collect do |assoc_name|
|
113
|
+
find_root_association_from_through_assocs(klass, assoc_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
destroy_association_names.uniq.each do |association_name|
|
117
|
+
add_deletion_dependency_to_flat_map(klass, association_name)
|
118
|
+
end
|
119
|
+
|
120
|
+
nullify_association_names.uniq.each do |association_name|
|
121
|
+
add_nullification_dependency_to_flat_map(klass, association_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
# add has_many relationships
|
125
|
+
(
|
126
|
+
klass.reflect_on_all_associations(:has_many) +
|
127
|
+
klass.reflect_on_all_associations(:has_one) +
|
128
|
+
klass.reflect_on_all_associations(:has_and_belongs_to_many)
|
129
|
+
).each do |reflection|
|
130
|
+
next if reflection.options[:through].present?
|
131
|
+
|
132
|
+
add_has_many_to_flat_map(klass, reflection)
|
133
|
+
end
|
134
|
+
|
135
|
+
# add belongs_to relationships
|
136
|
+
klass.reflect_on_all_associations(:belongs_to).each do |reflection|
|
137
|
+
next if reflection.options[:through].present?
|
138
|
+
|
139
|
+
add_belongs_to_to_flat_map(klass, reflection)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param klass [ActiveRecord::Base]
|
144
|
+
# @param association_name [Symbol] - name of the association
|
145
|
+
def add_has_many_to_flat_map(klass, reflection)
|
146
|
+
association_name = reflection.name
|
147
|
+
@flat_dependencies_per_klass[klass.name][:has_many][association_name] = reflection.klass.name
|
148
|
+
end
|
149
|
+
|
150
|
+
# @param klass [ActiveRecord::Base]
|
151
|
+
# @param association_name [Symbol] - name of the association
|
152
|
+
def add_belongs_to_to_flat_map(klass, reflection)
|
153
|
+
association_name = reflection.name
|
154
|
+
reflection_klass_name = is_reflection_polymorphic?(reflection) ? POLY_KLASS_NAME : reflection.klass.name
|
155
|
+
@flat_dependencies_per_klass[klass.name][:belongs_to][association_name] = reflection_klass_name
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param klass [ActiveRecord::Base]
|
159
|
+
# @param association_name [Symbol] - name of the association
|
160
|
+
def add_deletion_dependency_to_flat_map(klass, association_name)
|
161
|
+
reflection = klass.reflect_on_association(association_name)
|
162
|
+
reflection_klass_name = is_reflection_polymorphic?(reflection) ? POLY_KLASS_NAME : reflection.klass.name
|
163
|
+
@flat_dependencies_per_klass[klass.name][:destroy_dependencies][association_name] = reflection_klass_name
|
164
|
+
end
|
165
|
+
|
166
|
+
# @param klass [ActiveRecord::Base]
|
167
|
+
# @param association_name [Symbol] - name of the association
|
168
|
+
def add_nullification_dependency_to_flat_map(klass, association_name)
|
169
|
+
reflection = klass.reflect_on_association(association_name)
|
170
|
+
# nullifications can't be poly klass
|
171
|
+
@flat_dependencies_per_klass[klass.name][:nullify_dependencies][association_name] = reflection.klass.name
|
172
|
+
end
|
173
|
+
|
174
|
+
# @param reflection [ActiveRecord::Reflection::AssociationReflection]
|
175
|
+
def is_reflection_polymorphic?(reflection)
|
176
|
+
reflection.options&.dig(:polymorphic) == true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -9,6 +9,7 @@ module BulkDependencyEraser
|
|
9
9
|
|
10
10
|
delegate :nullification_list, :deletion_list, to: :dependency_builder
|
11
11
|
delegate :ignore_table_deletion_list, :ignore_table_nullification_list, to: :dependency_builder
|
12
|
+
delegate :circular_dependency_klasses, :flat_dependencies_per_klass, to: :dependency_builder
|
12
13
|
|
13
14
|
# @param query [ActiveRecord::Base | ActiveRecord::Relation]
|
14
15
|
def initialize query:, opts: {}
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module BulkDependencyEraser
|
2
2
|
class Nullifier < Base
|
3
|
-
DEFAULT_DB_NULLIFY_ALL_WRAPPER =
|
3
|
+
DEFAULT_DB_NULLIFY_ALL_WRAPPER = ->(nullifier, block) do
|
4
4
|
begin
|
5
5
|
block.call
|
6
6
|
rescue StandardError => e
|
7
|
-
report_error("Issue attempting to nullify
|
7
|
+
nullifier.report_error("Issue attempting to nullify: #{e.class.name} - #{e.message}")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -211,7 +211,7 @@ module BulkDependencyEraser
|
|
211
211
|
|
212
212
|
def nullify_all_in_db(&block)
|
213
213
|
puts "Nullifying all from DB..." if opts_c.verbose
|
214
|
-
opts_c.db_nullify_all_wrapper.call(block)
|
214
|
+
opts_c.db_nullify_all_wrapper.call(self, block)
|
215
215
|
puts "Nullifying all from DB complete." if opts_c.verbose
|
216
216
|
end
|
217
217
|
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module BulkDependencyEraser
|
2
|
+
class QuerySchemaParser < Base
|
3
|
+
DEFAULT_OPTS = {
|
4
|
+
verbose: false,
|
5
|
+
# Some associations scopes take parameters.
|
6
|
+
# - We would have to instantiate if we wanted to apply that scope filter.
|
7
|
+
instantiate_if_assoc_scope_with_arity: false,
|
8
|
+
force_destroy_restricted: false,
|
9
|
+
}
|
10
|
+
|
11
|
+
# attr_accessor :deletion_list, :nullification_list
|
12
|
+
attr_reader :initial_class
|
13
|
+
attr_reader :dependencies_per_klass
|
14
|
+
attr_reader :circular_dependency_klasses
|
15
|
+
attr_reader :full_schema_parser
|
16
|
+
|
17
|
+
delegate :flat_dependencies_per_klass, to: :full_schema_parser
|
18
|
+
|
19
|
+
def initialize query:, opts: {}
|
20
|
+
if query.is_a?(ActiveRecord::Relation)
|
21
|
+
@initial_class = query.klass
|
22
|
+
else
|
23
|
+
# current_query is a normal rails class
|
24
|
+
@initial_class = query
|
25
|
+
end
|
26
|
+
# @dependencies_per_klass Structure
|
27
|
+
# {
|
28
|
+
# <ActiveRecord::Base> => {
|
29
|
+
# <ActiveRecord::Reflection::AssociationReflection> => <ActiveRecord::Base>
|
30
|
+
# }
|
31
|
+
# }
|
32
|
+
@dependencies_per_klass = {}
|
33
|
+
# @circular_dependency_klasses Structure
|
34
|
+
# {
|
35
|
+
# <ActiveRecord::Base> => [
|
36
|
+
# # Path of dependencies that start and end with the key class
|
37
|
+
# <ActiveRecord::Base>,
|
38
|
+
# <ActiveRecord::Base>,
|
39
|
+
# <ActiveRecord::Base>,
|
40
|
+
# ]
|
41
|
+
# }
|
42
|
+
@circular_dependency_klasses = {}
|
43
|
+
@full_schema_parser = BulkDependencyEraser::FullSchemaParser.new(opts:)
|
44
|
+
super(opts:)
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute
|
48
|
+
unless full_schema_parser.execute
|
49
|
+
merge_errors(full_schema_parser.errors, 'FullSchemaParser: ')
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
klass_dependencies_parser(initial_class, klass_action: :destroy)
|
53
|
+
|
54
|
+
@dependencies_per_klass.each do |key, values|
|
55
|
+
@dependencies_per_klass[key] = values.uniq
|
56
|
+
end
|
57
|
+
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param klass [ActiveRecord::Base, Array<ActiveRecord::Base>]
|
62
|
+
# - if was a dependency from a polymophic class, then iterate through the klasses.
|
63
|
+
# @param dependency_path [Array<ActiveRecord::Base>] - previously parsed klasses
|
64
|
+
def klass_dependencies_parser klass, klass_action:, dependency_path: []
|
65
|
+
if klass.is_a?(Array)
|
66
|
+
klass.each do |klass_subset|
|
67
|
+
klass_dependencies_parser(klass_subset, klass_action:, dependency_path:)
|
68
|
+
end
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
unless DEPENDENCY_DESTROY.include?(klass_action) || DEPENDENCY_NULLIFY.include?(klass_action)
|
73
|
+
raise "invalid klass action: #{klass_action}"
|
74
|
+
end
|
75
|
+
raise "invalid klass: #{klass}" unless klass < ActiveRecord::Base
|
76
|
+
|
77
|
+
# Not a circular dependency if the repetitious klass has a nullify action.
|
78
|
+
if DEPENDENCY_DESTROY.include?(klass_action) && dependency_path.include?(klass.name)
|
79
|
+
index = dependency_path.index(klass.name)
|
80
|
+
circular_dependency = dependency_path[index..] + [klass.name]
|
81
|
+
circular_dependency_klasses[klass.name] = circular_dependency
|
82
|
+
return
|
83
|
+
end
|
84
|
+
|
85
|
+
# We don't need to consider dependencies for a klass that is being nullified.
|
86
|
+
return if DEPENDENCY_NULLIFY.include?(klass_action)
|
87
|
+
|
88
|
+
# already parsed, doesn't need to be parsed again.
|
89
|
+
return if dependencies_per_klass.include?(klass.name)
|
90
|
+
|
91
|
+
@dependencies_per_klass[klass.name] = []
|
92
|
+
|
93
|
+
# We're including :restricted dependencies
|
94
|
+
destroy_associations = klass.reflect_on_all_associations.select do |reflection|
|
95
|
+
dependency_type = reflection.options&.dig(:dependent)&.to_sym
|
96
|
+
DEPENDENCY_DESTROY.include?(dependency_type)
|
97
|
+
end
|
98
|
+
|
99
|
+
nullify_associations = klass.reflect_on_all_associations.select do |reflection|
|
100
|
+
dependency_type = reflection.options&.dig(:dependent)&.to_sym
|
101
|
+
DEPENDENCY_NULLIFY.include?(dependency_type)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Iterate through the assoc names, if there are any :through assocs, then rename the association
|
105
|
+
# - Rails interpretation of any dependencies of a :through association is to apply it to
|
106
|
+
# the leaf association at the end of the :through chain(s)
|
107
|
+
association_dependencies = {}
|
108
|
+
(
|
109
|
+
destroy_associations.map(&:name) +
|
110
|
+
nullify_associations.map(&:name)
|
111
|
+
).collect do |assoc_name|
|
112
|
+
root_association_name = find_root_association_from_through_assocs(klass, assoc_name)
|
113
|
+
association_dependencies[root_association_name] = klass.reflect_on_association(assoc_name).options.dig(:dependent)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Using association names as keys helps remove duplicates - from dependent options on through associations and root associations.
|
117
|
+
association_dependencies.each do |association_name, dependency_type|
|
118
|
+
association_parser(klass, association_name, dependency_type, dependency_path)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Used to iterate through each destroyable association, and recursively call 'deletion_query_parser'.
|
123
|
+
# @param parent_class [ApplicationRecord]
|
124
|
+
# @param association_name [Symbol] - The association name from the parent_class
|
125
|
+
def association_parser(parent_class, association_name, dependency_type, dependency_path)
|
126
|
+
reflection = parent_class.reflect_on_association(association_name)
|
127
|
+
reflection_type = reflection.class.name
|
128
|
+
|
129
|
+
raise "No dependency set for #{parent_class} and it's association: #{association_name}" unless dependency_type
|
130
|
+
|
131
|
+
case reflection_type
|
132
|
+
when 'ActiveRecord::Reflection::HasManyReflection'
|
133
|
+
association_parser_has_many(parent_class, association_name, dependency_type, dependency_path)
|
134
|
+
when 'ActiveRecord::Reflection::HasOneReflection'
|
135
|
+
association_parser_has_many(parent_class, association_name, dependency_type, dependency_path)
|
136
|
+
when 'ActiveRecord::Reflection::BelongsToReflection'
|
137
|
+
association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path)
|
138
|
+
else
|
139
|
+
report_message("Unsupported association type for #{parent_class.name}'s association '#{association_name}': #{reflection_type}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handles the :has_many association type
|
144
|
+
# - handles it's polymorphic associations internally (easier on the has_many)
|
145
|
+
def association_parser_has_many(parent_class, association_name, dependency_type, dependency_path)
|
146
|
+
raise "parent_class missing" unless parent_class
|
147
|
+
raise "#{parent_class} - association_name: missing" unless association_name
|
148
|
+
raise "#{parent_class} - dependency_type: missing" unless dependency_type
|
149
|
+
raise "#{parent_class} - dependency_path: nil" if dependency_path.nil?
|
150
|
+
|
151
|
+
reflection = parent_class.reflect_on_association(association_name)
|
152
|
+
reflection_type = reflection.class.name
|
153
|
+
|
154
|
+
assoc_klass = reflection.klass
|
155
|
+
assoc_klass_name = assoc_klass.name
|
156
|
+
@dependencies_per_klass[parent_class.name] << assoc_klass.name
|
157
|
+
|
158
|
+
# If there is an association scope present, check to see how many parameters it's using
|
159
|
+
# - if there's any parameter, we have to either skip it or instantiate it to find it's dependencies.
|
160
|
+
if reflection.scope&.arity&.nonzero? && opts_c.instantiate_if_assoc_scope_with_arity == false
|
161
|
+
report_error(
|
162
|
+
"#{parent_class.name} and '#{association_name}' - scope has instance parameters. Use :instantiate_if_assoc_scope_with_arity option?"
|
163
|
+
)
|
164
|
+
return
|
165
|
+
end
|
166
|
+
|
167
|
+
# Look for manually specified keys in the assocation first
|
168
|
+
specified_primary_key = reflection.options[:primary_key]&.to_s
|
169
|
+
specified_foreign_key = reflection.options[:foreign_key]&.to_s
|
170
|
+
# For polymorphic_associations
|
171
|
+
specified_foreign_type = nil
|
172
|
+
|
173
|
+
# handle foreign_key edge cases
|
174
|
+
if specified_foreign_key.nil?
|
175
|
+
if reflection.options[:as]
|
176
|
+
specified_foreign_type = "#{reflection.options[:as]}_type"
|
177
|
+
specified_foreign_key = "#{reflection.options[:as]}_id"
|
178
|
+
else
|
179
|
+
specified_foreign_key = parent_class.table_name.singularize + "_id"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Check to see if foreign_key exists in association class's table
|
184
|
+
unless assoc_klass.column_names.include?(specified_foreign_key)
|
185
|
+
report_error(
|
186
|
+
"
|
187
|
+
For '#{assoc_klass.name}': Could not determine the assoc's foreign key.
|
188
|
+
Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{assoc_klass.table_name} table.
|
189
|
+
"
|
190
|
+
)
|
191
|
+
return
|
192
|
+
end
|
193
|
+
|
194
|
+
unless specified_foreign_type.nil? || assoc_klass.column_names.include?(specified_foreign_type)
|
195
|
+
report_error(
|
196
|
+
"
|
197
|
+
For '#{assoc_klass.name}': Could not determine the assoc's foreign key type.
|
198
|
+
Foreign key type should have been '#{specified_foreign_type}', but did not exist on the #{assoc_klass.table_name} table.
|
199
|
+
"
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
if DEPENDENCY_RESTRICT.include?(dependency_type) && traverse_restricted_dependency?(parent_class, reflection)
|
204
|
+
klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name)
|
205
|
+
else
|
206
|
+
klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def association_parser_belongs_to(parent_class, association_name, dependency_type, dependency_path)
|
211
|
+
raise "parent_class missing" unless parent_class
|
212
|
+
raise "#{parent_class} - association_name: missing" unless association_name
|
213
|
+
raise "#{parent_class} - dependency_type: missing" unless dependency_type
|
214
|
+
raise "#{parent_class} - dependency_path: nil" if dependency_path.nil?
|
215
|
+
|
216
|
+
reflection = parent_class.reflect_on_association(association_name)
|
217
|
+
reflection_type = reflection.class.name
|
218
|
+
|
219
|
+
is_polymorphic = reflection.options[:polymorphic]
|
220
|
+
if is_polymorphic
|
221
|
+
assoc_klass = find_klasses_from_polymorphic_dependency(parent_class).map(&:constantize)
|
222
|
+
@dependencies_per_klass[parent_class.name] += assoc_klass.map(&:name)
|
223
|
+
else
|
224
|
+
assoc_klass = reflection.klass
|
225
|
+
@dependencies_per_klass[parent_class.name] << assoc_klass.name
|
226
|
+
end
|
227
|
+
|
228
|
+
specified_primary_key = reflection.options[:primary_key] || 'id'
|
229
|
+
specified_foreign_key = reflection.options[:foreign_key] || "#{association_name}_id"
|
230
|
+
|
231
|
+
# Check to see if foreign_key exists in our parent table
|
232
|
+
unless parent_class.column_names.include?(specified_foreign_key)
|
233
|
+
report_error(
|
234
|
+
"
|
235
|
+
For #{parent_class.name}'s association '#{association_name}': Could not determine the assoc's foreign key.
|
236
|
+
Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{parent_class.table_name} table.
|
237
|
+
"
|
238
|
+
)
|
239
|
+
return
|
240
|
+
end
|
241
|
+
|
242
|
+
if (
|
243
|
+
DEPENDENCY_DESTROY.include?(dependency_type) ||
|
244
|
+
DEPENDENCY_NULLIFY.include?(dependency_type) && traverse_restricted_dependency?(parent_class, reflection)
|
245
|
+
)
|
246
|
+
klass_dependencies_parser(assoc_klass, klass_action: dependency_type, dependency_path: dependency_path.dup << parent_class.name)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# In this example the klass would be the polymorphic klass
|
251
|
+
# - i.e. Attachment belongs_to: :attachable, dependent: :destroy
|
252
|
+
# We're looking for klasses in the flat map that have a has_many :attachments, as: :attachable
|
253
|
+
def find_klasses_from_polymorphic_dependency(klass)
|
254
|
+
found_klasses = []
|
255
|
+
flat_dependencies_per_klass.each do |flat_klass_name, klass_dependencies|
|
256
|
+
if klass_dependencies[:has_many].values.include?(klass.name)
|
257
|
+
found_klasses << flat_klass_name
|
258
|
+
end
|
259
|
+
end
|
260
|
+
found_klasses
|
261
|
+
end
|
262
|
+
|
263
|
+
# return [Boolean]
|
264
|
+
# - true if valid
|
265
|
+
# - false if not valid
|
266
|
+
def traverse_restricted_dependency? parent_class, reflection
|
267
|
+
# Return true if we're going to destroy all restricted
|
268
|
+
return true if opts_c.force_destroy_restricted
|
269
|
+
|
270
|
+
report_error(
|
271
|
+
"
|
272
|
+
#{parent_class.name}'s assoc '#{reflection.name}' has a restricted dependency type.
|
273
|
+
If you still wish to destroy, use the 'force_destroy_restricted: true' option
|
274
|
+
"
|
275
|
+
)
|
276
|
+
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BulkDependencyEraser
|
2
|
+
module Utils
|
3
|
+
module Methods
|
4
|
+
# To freeze all nested structures including hashes, arrays, and strings
|
5
|
+
# Deep Freezing All Structures
|
6
|
+
def deep_freeze(obj)
|
7
|
+
case obj
|
8
|
+
when Hash
|
9
|
+
obj.each { |key, value| deep_freeze(key); deep_freeze(value) }
|
10
|
+
obj.freeze
|
11
|
+
when Array
|
12
|
+
obj.each { |value| deep_freeze(value) }
|
13
|
+
obj.freeze
|
14
|
+
when String
|
15
|
+
obj.freeze
|
16
|
+
else
|
17
|
+
obj.freeze if obj.respond_to?(:freeze)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
include Methods
|
23
|
+
extend Methods
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require_relative 'bulk_dependency_eraser/base'
|
2
2
|
require_relative 'bulk_dependency_eraser/builder'
|
3
3
|
require_relative 'bulk_dependency_eraser/deleter'
|
4
|
+
require_relative 'bulk_dependency_eraser/full_schema_parser'
|
4
5
|
require_relative 'bulk_dependency_eraser/nullifier'
|
6
|
+
require_relative 'bulk_dependency_eraser/query_schema_parser'
|
5
7
|
require_relative 'bulk_dependency_eraser/manager'
|
8
|
+
require_relative 'bulk_dependency_eraser/utils'
|
6
9
|
require_relative 'bulk_dependency_eraser/version'
|
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: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- benjamin.dana.software.dev@gmail.com
|
@@ -146,8 +146,11 @@ files:
|
|
146
146
|
- lib/bulk_dependency_eraser/base.rb
|
147
147
|
- lib/bulk_dependency_eraser/builder.rb
|
148
148
|
- lib/bulk_dependency_eraser/deleter.rb
|
149
|
+
- lib/bulk_dependency_eraser/full_schema_parser.rb
|
149
150
|
- lib/bulk_dependency_eraser/manager.rb
|
150
151
|
- lib/bulk_dependency_eraser/nullifier.rb
|
152
|
+
- lib/bulk_dependency_eraser/query_schema_parser.rb
|
153
|
+
- lib/bulk_dependency_eraser/utils.rb
|
151
154
|
- lib/bulk_dependency_eraser/version.rb
|
152
155
|
homepage: https://github.com/danabr75/bulk_dependency_eraser
|
153
156
|
licenses:
|