bulk_dependency_eraser 2.1.0 → 2.2.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 +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:
|