bulk_dependency_eraser 1.0.2 → 1.0.3
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc8c21921516530d646fff41d72ce0157e1d706373eefea4da7ea924bb1cb9b9
|
|
4
|
+
data.tar.gz: 9501007142223dfad5cb05e89ab38162499b564eaaf3636074e3cd54a573592f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f461d596eb9160828f0905dde68396ade1409f8f9c17de2e4bb8677cb12695be59c75f6fa33d84d9156a2b10fdab2eed869b7f19a79c84e1526c705ac4cf58b
|
|
7
|
+
data.tar.gz: 64777768e6eb815d575d11690cb793962ab4fcb24911bc2bb209d5d7a33d3bc660b6d939fe287ca2c41dcac8524efe95032f3b6ee76272956391589193315666
|
|
@@ -30,7 +30,9 @@ module BulkDependencyEraser
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def report_error msg
|
|
33
|
-
|
|
33
|
+
# remove new lines, surrounding white space, replace with semicolon delimiters
|
|
34
|
+
n = msg.strip.gsub(/\s*\n\s*/, ' ')
|
|
35
|
+
@errors << n
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def merge_errors errors, prefix = nil
|
|
@@ -13,6 +13,7 @@ module BulkDependencyEraser
|
|
|
13
13
|
# Won't parse any table in this list
|
|
14
14
|
ignore_tables_and_dependencies: [],
|
|
15
15
|
ignore_klass_names_and_dependencies: [],
|
|
16
|
+
batching_size_limit: 500,
|
|
16
17
|
}.freeze
|
|
17
18
|
|
|
18
19
|
DEFAULT_DB_WRAPPER = ->(block) do
|
|
@@ -106,11 +107,18 @@ module BulkDependencyEraser
|
|
|
106
107
|
#{e.message}
|
|
107
108
|
"
|
|
108
109
|
)
|
|
110
|
+
raise e
|
|
109
111
|
|
|
110
112
|
return false
|
|
111
113
|
end
|
|
112
114
|
end
|
|
113
115
|
|
|
116
|
+
protected
|
|
117
|
+
|
|
118
|
+
attr_reader :ignore_klass_and_dependencies
|
|
119
|
+
attr_reader :table_names_to_parsed_klass_names
|
|
120
|
+
attr_reader :ignore_table_name_and_dependencies, :ignore_klass_name_and_dependencies
|
|
121
|
+
|
|
114
122
|
def deletion_query_parser query, association_parent = nil
|
|
115
123
|
# necessary for "ActiveRecord::Reflection::ThroughReflection" use-case
|
|
116
124
|
# force_through_destroy_chains = options[:force_destroy_chain] || {}
|
|
@@ -145,7 +153,7 @@ module BulkDependencyEraser
|
|
|
145
153
|
if association_parent
|
|
146
154
|
puts "Building #{klass_name}"
|
|
147
155
|
else
|
|
148
|
-
puts "Building #{association_parent}
|
|
156
|
+
puts "Building #{association_parent}, assocation of #{klass_name}"
|
|
149
157
|
end
|
|
150
158
|
end
|
|
151
159
|
|
|
@@ -184,19 +192,12 @@ module BulkDependencyEraser
|
|
|
184
192
|
# ignore associations that aren't a dependent destroyable type
|
|
185
193
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
|
186
194
|
assoc_dependent_type = reflection.options&.dig(:dependent)&.to_sym
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"#{klass_name}'s assoc '#{reflection.name}' has a 'dependent: :#{assoc_dependent_type}' set. " \
|
|
194
|
-
"If you still wish to destroy, use the 'force_destroy_restricted: true' option"
|
|
195
|
-
)
|
|
196
|
-
false
|
|
197
|
-
else
|
|
198
|
-
DEPENDENCY_DESTROY.include?(assoc_dependent_type)
|
|
199
|
-
end
|
|
195
|
+
DEPENDENCY_DESTROY.include?(assoc_dependent_type) && !DEPENDENCY_RESTRICT.include?(assoc_dependent_type)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
restricted_associations = query.reflect_on_all_associations.select do |reflection|
|
|
199
|
+
assoc_dependent_type = reflection.options&.dig(:dependent)&.to_sym
|
|
200
|
+
DEPENDENCY_RESTRICT.include?(assoc_dependent_type)
|
|
200
201
|
end
|
|
201
202
|
|
|
202
203
|
nullify_associations = query.reflect_on_all_associations.select do |reflection|
|
|
@@ -204,8 +205,15 @@ module BulkDependencyEraser
|
|
|
204
205
|
DEPENDENCY_NULLIFY.include?(assoc_dependent_type)
|
|
205
206
|
end
|
|
206
207
|
|
|
207
|
-
destroy_association_names
|
|
208
|
-
nullify_association_names
|
|
208
|
+
destroy_association_names = destroy_associations.map(&:name)
|
|
209
|
+
nullify_association_names = nullify_associations.map(&:name)
|
|
210
|
+
restricted_association_names = restricted_associations.map(&:name)
|
|
211
|
+
|
|
212
|
+
if opts_c.verbose
|
|
213
|
+
puts "Destroyable Associations: #{destroy_association_names}"
|
|
214
|
+
puts "Nullifiable Associations: #{nullify_association_names}"
|
|
215
|
+
puts " Restricted Associations: #{restricted_association_names}"
|
|
216
|
+
end
|
|
209
217
|
|
|
210
218
|
# Iterate through the assoc names, if there are any :through assocs, then remap
|
|
211
219
|
destroy_association_names = destroy_association_names.collect do |assoc_name|
|
|
@@ -222,12 +230,49 @@ module BulkDependencyEraser
|
|
|
222
230
|
nullify_association_names.each do |nullify_association_name|
|
|
223
231
|
association_parser(klass, query, query_ids, nullify_association_name, :nullify)
|
|
224
232
|
end
|
|
233
|
+
|
|
234
|
+
restricted_association_names.each do |restricted_association_name|
|
|
235
|
+
association_parser(klass, query, query_ids, restricted_association_name, :restricted)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Used to iterate through each destroyable association, and recursively call 'deletion_query_parser'.
|
|
240
|
+
# @param parent_class [ApplicationRecord]
|
|
241
|
+
# @param query [ActiveRecord_Relation] - We need the 'query' in case associations are tied to column other than 'id'
|
|
242
|
+
# @param query_ids [Array<Int | String>] - Array of parent's IDs (or UUIDs)
|
|
243
|
+
# @param association_name [Symbol] - The association name from the parent_class
|
|
244
|
+
# @param type [Symbol] - either :delete or :nullify or :restricted
|
|
245
|
+
def association_parser(parent_class, query, query_ids, association_name, type)
|
|
246
|
+
reflection = parent_class.reflect_on_association(association_name)
|
|
247
|
+
reflection_type = reflection.class.name
|
|
248
|
+
|
|
249
|
+
if self.class::DEPENDENCY_DESTROY_IGNORE_REFLECTION_TYPES.include?(reflection_type)
|
|
250
|
+
report_error("Dependency detected on #{parent_class.name}'s '#{association_name}' - association doesn't support dependency")
|
|
251
|
+
return
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
case reflection_type
|
|
255
|
+
when 'ActiveRecord::Reflection::HasManyReflection'
|
|
256
|
+
association_parser_has_many(parent_class, query, query_ids, association_name, type)
|
|
257
|
+
when 'ActiveRecord::Reflection::HasOneReflection'
|
|
258
|
+
association_parser_has_many(parent_class, query, query_ids, association_name, type, { limit: 1 })
|
|
259
|
+
when 'ActiveRecord::Reflection::BelongsToReflection'
|
|
260
|
+
if type == :nullify
|
|
261
|
+
report_error("#{parent_class.name}'s association '#{association_name}' - dependent 'nullify' invalid for 'belongs_to'")
|
|
262
|
+
else
|
|
263
|
+
association_parser_belongs_to(parent_class, query, query_ids, association_name, type)
|
|
264
|
+
end
|
|
265
|
+
else
|
|
266
|
+
report_message("Unsupported association type for #{parent_class.name}'s association '#{association_name}': #{reflection_type}")
|
|
267
|
+
end
|
|
225
268
|
end
|
|
226
269
|
|
|
227
|
-
#
|
|
228
|
-
|
|
270
|
+
# Handles the :has_many association type
|
|
271
|
+
# - handles it's polymorphic associations internally (easier on the has_many)
|
|
272
|
+
def association_parser_has_many(parent_class, query, query_ids, association_name, type, opts = {})
|
|
229
273
|
reflection = parent_class.reflect_on_association(association_name)
|
|
230
274
|
reflection_type = reflection.class.name
|
|
275
|
+
|
|
231
276
|
assoc_klass = reflection.klass
|
|
232
277
|
assoc_klass_name = assoc_klass.name
|
|
233
278
|
|
|
@@ -241,9 +286,9 @@ module BulkDependencyEraser
|
|
|
241
286
|
# If there is an association scope present, check to see how many parameters it's using
|
|
242
287
|
# - if there's any parameter, we have to either skip it or instantiate it to find it's dependencies.
|
|
243
288
|
if reflection.scope&.arity&.nonzero?
|
|
244
|
-
# TODO!
|
|
245
289
|
if opts_c.instantiate_if_assoc_scope_with_arity
|
|
246
|
-
|
|
290
|
+
association_parser_has_many_instantiation(parent_class, query, query_ids, association_name, type, opts)
|
|
291
|
+
return
|
|
247
292
|
else
|
|
248
293
|
report_error(
|
|
249
294
|
"#{parent_class.name} and '#{association_name}' - scope has instance parameters. Use :instantiate_if_assoc_scope_with_arity option?"
|
|
@@ -252,21 +297,23 @@ module BulkDependencyEraser
|
|
|
252
297
|
end
|
|
253
298
|
elsif reflection.scope
|
|
254
299
|
# I saw this used somewhere, too bad I didn't save the source for it.
|
|
255
|
-
|
|
256
|
-
assoc_query = assoc_query.instance_exec(&
|
|
300
|
+
assoc_scope = parent_class.reflect_on_association(association_name).scope
|
|
301
|
+
assoc_query = assoc_query.instance_exec(&assoc_scope)
|
|
257
302
|
end
|
|
258
303
|
|
|
304
|
+
# Look for manually specified keys in the assocation first
|
|
259
305
|
specified_primary_key = reflection.options[:primary_key]&.to_s
|
|
260
306
|
specified_foreign_key = reflection.options[:foreign_key]&.to_s
|
|
307
|
+
# For polymorphic_associations
|
|
308
|
+
specified_foreign_type = nil
|
|
261
309
|
|
|
262
310
|
# handle foreign_key edge cases
|
|
263
311
|
if specified_foreign_key.nil?
|
|
264
|
-
if reflection.options[:
|
|
265
|
-
|
|
266
|
-
specified_foreign_key =
|
|
267
|
-
|
|
268
|
-
assoc_query = assoc_query.where({
|
|
269
|
-
specified_foreign_key = reflection.options[:as].to_s + "_id"
|
|
312
|
+
if reflection.options[:as]
|
|
313
|
+
specified_foreign_type = "#{reflection.options[:as]}_type"
|
|
314
|
+
specified_foreign_key = "#{reflection.options[:as]}_id"
|
|
315
|
+
# Only filtering by type here, the extra work for a poly assoc. We filter by IDs later
|
|
316
|
+
assoc_query = assoc_query.where({ specified_foreign_type.to_sym => parent_class.name })
|
|
270
317
|
else
|
|
271
318
|
specified_foreign_key = parent_class.table_name.singularize + "_id"
|
|
272
319
|
end
|
|
@@ -277,7 +324,7 @@ module BulkDependencyEraser
|
|
|
277
324
|
report_error(
|
|
278
325
|
"
|
|
279
326
|
For #{parent_class.name}'s assoc '#{assoc_klass.name}': Could not determine the assoc's foreign key.
|
|
280
|
-
|
|
327
|
+
Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{assoc_klass.table_name} table.
|
|
281
328
|
"
|
|
282
329
|
)
|
|
283
330
|
return
|
|
@@ -295,9 +342,18 @@ module BulkDependencyEraser
|
|
|
295
342
|
assoc_query = assoc_query.where(specified_foreign_key.to_sym => query_ids)
|
|
296
343
|
end
|
|
297
344
|
|
|
345
|
+
# Apply limit if has_one assocation (subset of has_many)
|
|
346
|
+
if opts[:limit]
|
|
347
|
+
assoc_query = assoc_query.limit(opts[:limit])
|
|
348
|
+
end
|
|
349
|
+
|
|
298
350
|
if type == :delete
|
|
299
351
|
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
|
300
352
|
deletion_query_parser(assoc_query, parent_class)
|
|
353
|
+
elsif type == :restricted
|
|
354
|
+
if traverse_restricted_dependency?(parent_class, reflection, assoc_query)
|
|
355
|
+
deletion_query_parser(assoc_query, parent_class)
|
|
356
|
+
end
|
|
301
357
|
elsif type == :nullify
|
|
302
358
|
# No need for recursion here.
|
|
303
359
|
# - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
|
@@ -312,16 +368,193 @@ module BulkDependencyEraser
|
|
|
312
368
|
nullification_list[assoc_klass_name][specified_foreign_key] ||= []
|
|
313
369
|
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
|
314
370
|
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
|
371
|
+
|
|
372
|
+
nullification_list[assoc_klass_name][specified_foreign_key].sort! if Rails.env.test?
|
|
373
|
+
|
|
374
|
+
# Also nullify the 'type' field, if the association is polymorphic
|
|
375
|
+
if specified_foreign_type
|
|
376
|
+
nullification_list[assoc_klass_name][specified_foreign_type] ||= []
|
|
377
|
+
nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
|
|
378
|
+
nullification_list[assoc_klass_name][specified_foreign_type].uniq!
|
|
379
|
+
|
|
380
|
+
nullification_list[assoc_klass_name][specified_foreign_type].sort! if Rails.env.test?
|
|
381
|
+
end
|
|
315
382
|
else
|
|
316
383
|
raise "invalid parsing type: #{type}"
|
|
317
384
|
end
|
|
318
385
|
end
|
|
319
386
|
|
|
320
|
-
|
|
387
|
+
# So you've decided to attempt instantiation...
|
|
388
|
+
# This will be a lot slower than the rest of our logic here, but if needs must.
|
|
389
|
+
#
|
|
390
|
+
# This method will replicate association_parser, but instantiate and iterate in batches
|
|
391
|
+
def association_parser_has_many_instantiation(parent_class, query, query_ids, association_name, type, opts)
|
|
392
|
+
raise "Invalid State! Not ready to instantiate!"
|
|
393
|
+
reflection = parent_class.reflect_on_association(association_name)
|
|
394
|
+
reflection_type = reflection.class.name
|
|
395
|
+
assoc_klass = reflection.klass
|
|
396
|
+
assoc_klass_name = assoc_klass.name
|
|
321
397
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
|
|
399
|
+
# specified_primary_key = reflection.options[:primary_key]&.to_s
|
|
400
|
+
# specified_foreign_key = reflection.options[:foreign_key]&.to_s
|
|
401
|
+
|
|
402
|
+
# assoc_query = assoc_klass.unscoped
|
|
403
|
+
# query.in_batches
|
|
404
|
+
|
|
405
|
+
assoc_klass.in_batches(of: opts_c.batching_size_limit) do |batch|
|
|
406
|
+
batch.each do |record|
|
|
407
|
+
record.send(association_name)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def association_parser_belongs_to(parent_class, query, query_ids, association_name, type)
|
|
413
|
+
reflection = parent_class.reflect_on_association(association_name)
|
|
414
|
+
reflection_type = reflection.class.name
|
|
415
|
+
|
|
416
|
+
# Can't run certain checks on a polymorphic association, no definitive klass to use.
|
|
417
|
+
# - Usually, the polymorphic class is the leaf in a dependency tree.
|
|
418
|
+
# - In this case, i.e.: a `belongs_to :polymorphicable, polymorphic: true, dependent: :destroy` use-case
|
|
419
|
+
if reflection.options[:polymorphic]
|
|
420
|
+
# We'd have to pluck our various types, iterate through each, using each type as the assoc_query starting point
|
|
421
|
+
association_parser_belongs_to_polymorphic(parent_class, query, query_ids, association_name, type)
|
|
422
|
+
return
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
assoc_klass = reflection.klass
|
|
426
|
+
assoc_klass_name = assoc_klass.name
|
|
427
|
+
|
|
428
|
+
assoc_query = assoc_klass.unscoped
|
|
429
|
+
|
|
430
|
+
unless assoc_klass.primary_key == 'id'
|
|
431
|
+
report_error("#{parent_class.name}'s association '#{association_name}' - assoc class does not use 'id' as a primary_key")
|
|
432
|
+
return
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# If there is an association scope present, check to see how many parameters it's using
|
|
436
|
+
# - if there's any parameter, we have to either skip it or instantiate it to find it's dependencies.
|
|
437
|
+
if reflection.scope&.arity&.nonzero?
|
|
438
|
+
# TODO: PENDING:
|
|
439
|
+
# if opts_c.instantiate_if_assoc_scope_with_arity
|
|
440
|
+
# association_parser_belongs_to_instantiation(parent_class, query, query_ids, association_name, type)
|
|
441
|
+
# return
|
|
442
|
+
# else
|
|
443
|
+
# report_error(
|
|
444
|
+
# "#{parent_class.name} and '#{association_name}' - scope has instance parameters. Use :instantiate_if_assoc_scope_with_arity option?"
|
|
445
|
+
# )
|
|
446
|
+
# return
|
|
447
|
+
# end
|
|
448
|
+
elsif reflection.scope
|
|
449
|
+
# I saw this used somewhere, too bad I didn't save the source for it.
|
|
450
|
+
assoc_scope = parent_class.reflect_on_association(association_name).scope
|
|
451
|
+
assoc_query = assoc_query.instance_exec(&assoc_scope)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
specified_primary_key = reflection.options[:primary_key] || 'id'
|
|
455
|
+
specified_foreign_key = reflection.options[:foreign_key] || "#{association_name}_id"
|
|
456
|
+
|
|
457
|
+
# Check to see if foreign_key exists in our parent table
|
|
458
|
+
unless parent_class.column_names.include?(specified_foreign_key)
|
|
459
|
+
report_error(
|
|
460
|
+
"
|
|
461
|
+
For #{parent_class.name}'s association '#{association_name}': Could not determine the assoc's foreign key.
|
|
462
|
+
Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{parent_class.table_name} table.
|
|
463
|
+
"
|
|
464
|
+
)
|
|
465
|
+
return
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
assoc_query = read_from_db do
|
|
469
|
+
assoc_query.where(
|
|
470
|
+
specified_primary_key.to_sym => query.pluck(specified_foreign_key)
|
|
471
|
+
)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
if type == :delete
|
|
475
|
+
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
|
476
|
+
deletion_query_parser(assoc_query, parent_class)
|
|
477
|
+
elsif type == :restricted
|
|
478
|
+
if traverse_restricted_dependency?(parent_class, reflection, assoc_query)
|
|
479
|
+
deletion_query_parser(assoc_query, parent_class)
|
|
480
|
+
end
|
|
481
|
+
else
|
|
482
|
+
raise "invalid parsing type: #{type}"
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# So you've decided to attempt instantiation...
|
|
487
|
+
# This will be a lot slower than the rest of our logic here, but if needs must.
|
|
488
|
+
#
|
|
489
|
+
# This method will replicate association_parser, but instantiate and iterate in batches
|
|
490
|
+
def association_parser_belongs_to_instantiation(parent_class, query, query_ids, association_name, type)
|
|
491
|
+
# pending
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# In this case, it's like a `belongs_to :polymorphicable, polymorphic: true, dependent: :destroy` use-case
|
|
495
|
+
# - it's unusual, but valid use-case
|
|
496
|
+
def association_parser_belongs_to_polymorphic(parent_class, query, query_ids, association_name, type)
|
|
497
|
+
reflection = parent_class.reflect_on_association(association_name)
|
|
498
|
+
reflection_type = reflection.class.name
|
|
499
|
+
|
|
500
|
+
# If there is an association scope present, check to see how many parameters it's using
|
|
501
|
+
# - if there's any parameter, we have to either skip it or instantiate it to find it's dependencies.
|
|
502
|
+
if reflection.scope&.arity&.nonzero?
|
|
503
|
+
raise 'PENDING'
|
|
504
|
+
elsif reflection.scope
|
|
505
|
+
# I saw this used somewhere, too bad I didn't save the source for it.
|
|
506
|
+
assoc_scope = parent_class.reflect_on_association(association_name).scope
|
|
507
|
+
assoc_query = assoc_query.instance_exec(&assoc_scope)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
specified_primary_key = reflection.options[:primary_key] || 'id'
|
|
511
|
+
specified_foreign_key = reflection.options[:foreign_key] || "#{association_name}_id"
|
|
512
|
+
specified_foreign_type = specified_foreign_key.sub(/_id$/, '_type')
|
|
513
|
+
|
|
514
|
+
# Check to see if foreign_key exists in our parent table
|
|
515
|
+
unless parent_class.column_names.include?(specified_foreign_key)
|
|
516
|
+
report_error(
|
|
517
|
+
"
|
|
518
|
+
For #{parent_class.name}'s association '#{association_name}': Could not determine the class's foreign key.
|
|
519
|
+
Foreign key should have been '#{specified_foreign_key}', but did not exist on the #{parent_class.table_name} table.
|
|
520
|
+
"
|
|
521
|
+
)
|
|
522
|
+
return
|
|
523
|
+
end
|
|
524
|
+
unless parent_class.column_names.include?(specified_foreign_type)
|
|
525
|
+
report_error(
|
|
526
|
+
"
|
|
527
|
+
For #{parent_class.name}'s association '#{association_name}': Could not determine the class's polymorphic type.
|
|
528
|
+
Foreign key should have been '#{specified_foreign_type}', but did not exist on the #{parent_class.table_name} table.
|
|
529
|
+
"
|
|
530
|
+
)
|
|
531
|
+
return
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
foreign_ids_by_type = read_from_db do
|
|
535
|
+
query.pluck(specified_foreign_key, specified_foreign_type).each_with_object({}) do |(id, type), hash|
|
|
536
|
+
hash.key?(type) ? hash[type] << id : hash[type] = [id]
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
if type == :delete
|
|
541
|
+
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
|
542
|
+
foreign_ids_by_type.each do |type, ids|
|
|
543
|
+
assoc_klass = type.constantize
|
|
544
|
+
deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
|
|
545
|
+
end
|
|
546
|
+
elsif type == :restricted
|
|
547
|
+
if traverse_restricted_dependency_for_belongs_to_poly?(parent_class, reflection, foreign_ids_by_type)
|
|
548
|
+
# Recursively call 'deletion_query_parser' on association query, to delete any if the assoc's dependencies
|
|
549
|
+
foreign_ids_by_type.each do |type, ids|
|
|
550
|
+
assoc_klass = type.constantize
|
|
551
|
+
deletion_query_parser(assoc_klass.where(id: ids), assoc_klass)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
else
|
|
555
|
+
raise "invalid parsing type: #{type}"
|
|
556
|
+
end
|
|
557
|
+
end
|
|
325
558
|
|
|
326
559
|
# A dependent assoc may be through another association. Follow the throughs to find the correct assoc to destroy.
|
|
327
560
|
def find_root_association_from_through_assocs klass, association_name
|
|
@@ -334,6 +567,43 @@ module BulkDependencyEraser
|
|
|
334
567
|
end
|
|
335
568
|
end
|
|
336
569
|
|
|
570
|
+
# return [Boolean]
|
|
571
|
+
# - true if valid
|
|
572
|
+
# - false if not valid
|
|
573
|
+
def traverse_restricted_dependency? parent_class, reflection, association_query
|
|
574
|
+
# Return true if we're going to destroy all restricted
|
|
575
|
+
return true if opts_c.force_destroy_restricted
|
|
576
|
+
|
|
577
|
+
if association_query.any?
|
|
578
|
+
report_error(
|
|
579
|
+
"
|
|
580
|
+
#{parent_class.name}'s assoc '#{reflection.name}' has a restricted dependency type.
|
|
581
|
+
If you still wish to destroy, use the 'force_destroy_restricted: true' option
|
|
582
|
+
"
|
|
583
|
+
)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
return false
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# special use-case to detect restricted dependency for 'belongs_to polymorphic: true' use-case
|
|
590
|
+
def traverse_restricted_dependency_for_belongs_to_poly? parent_class, reflection, ids_by_type
|
|
591
|
+
# Return true if we're going to destroy all restricted
|
|
592
|
+
return true if opts_c.force_destroy_restricted
|
|
593
|
+
|
|
594
|
+
ids = ids_by_type.values.flatten
|
|
595
|
+
if ids.any?
|
|
596
|
+
report_error(
|
|
597
|
+
"
|
|
598
|
+
#{parent_class.name}'s assoc '#{reflection.name}' has a restricted dependency type.
|
|
599
|
+
If you still wish to destroy, use the 'force_destroy_restricted: true' option
|
|
600
|
+
"
|
|
601
|
+
)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
return false
|
|
605
|
+
end
|
|
606
|
+
|
|
337
607
|
def read_from_db(&block)
|
|
338
608
|
puts "Reading from DB..." if opts_c.verbose
|
|
339
609
|
opts_c.db_read_wrapper.call(block)
|
|
@@ -44,8 +44,8 @@ module BulkDependencyEraser
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
-
rescue
|
|
48
|
-
report_error("Issue attempting to delete '#{current_class_name}': #{e.name} - #{e.message}")
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
report_error("Issue attempting to delete '#{current_class_name}': #{e.class.name} - #{e.message}")
|
|
49
49
|
raise ActiveRecord::Rollback
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -47,8 +47,8 @@ module BulkDependencyEraser
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
|
-
rescue
|
|
51
|
-
report_error("Issue attempting to nullify '#{current_class_name}' column '#{current_column}': #{e.name} - #{e.message}")
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
report_error("Issue attempting to nullify '#{current_class_name}' column '#{current_column}': #{e.class.name} - #{e.message}")
|
|
52
52
|
raise ActiveRecord::Rollback
|
|
53
53
|
end
|
|
54
54
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bulk_dependency_eraser
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- benjamin.dana.software.dev@gmail.com
|
|
@@ -108,6 +108,34 @@ dependencies:
|
|
|
108
108
|
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '1.4'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: factory_bot
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '6.4'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '6.4'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: faker
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.4'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '3.4'
|
|
111
139
|
description:
|
|
112
140
|
email:
|
|
113
141
|
executables: []
|