Class: BulkDependencyEraser::Deleter

Inherits:
Base
  • Object
show all
Defined in:
lib/bulk_dependency_eraser/deleter.rb

Constant Summary collapse

DEFAULT_DB_DELETE_ALL_WRAPPER =
->(deleter, block) do
  begin
    block.call
  rescue BulkDependencyEraser::Errors::DeleterError => e
    deleter.report_error(
      "      Issue attempting to delete klass '\#{e.deleting_klass_name}'\n        => \#{e.original_error_klass.name}: \#{e.message}\n      STRING\n    )\n  end\nend\n".strip
DEFAULT_OPTS =
{
  verbose: false,
  # Runs once, all deletions occur within it
  # - useful if you wanted to implement a rollback:
  #   - i.e:
      # db_delete_all_wrapper: lambda do |block|
      #   ActiveRecord::Base.transaction do
      #     begin
      #       block.call
      #     rescue StandardError => e
      #       report_error("Issue attempting to delete '#{current_class_name}': #{e.class.name} - #{e.message}")
      #       raise ActiveRecord::Rollback
      #     end
      #   end
      # end
  db_delete_all_wrapper: self::DEFAULT_DB_DELETE_ALL_WRAPPER,
  db_delete_wrapper: self::DEFAULT_DB_WRITE_WRAPPER,
  # Set to true if you want 'ActiveRecord::InvalidForeignKey' errors raised during deletions
  enable_invalid_foreign_key_detection: false,
  disable_batching: false,
  # a general batching size
  batch_size: 300,
  # A specific batching size for this class, overrides the batch_size
  delete_batch_size: nil,
  # A specific batching size for this class, overrides the batch_size
  disable_delete_batching: nil,
  # Applied to all queries. Useful for taking advantage of specific indexes
  # - not indexed by klass name. Proc would handle the logic for that.
  # - 4th, and lowest, priority of scopes
  # - accepts rails query as parameter
  # - return nil if no applicable scope.
  proc_scopes: self::DEFAULT_SCOPE_WRAPPER,
  # Applied to all queries. Useful for taking advantage of specific indexes
  # - 3rd highest priority of scopes
  proc_scopes_per_class_name: {},
  # Applied to all queries. Useful for taking advantage of specific indexes
  # - 2nd highest priority of scopes
  deletion_proc_scopes: self::DEFAULT_SCOPE_WRAPPER,
  # Applied to deletion queries
  # - 1st priority of scopes
  deletion_proc_scopes_per_class_name: {},
  use_ctid_over_primary_key: false,
  # Using PG system column CTID can result in a 10x deletion speed increase
  # - is used in combination with the primary key column to ensure CTID hasn't changed.
  use_pg_system_column_ctid: false,
  delete_ctids_by_partions: false,
}.freeze

Constants inherited from Base

Base::DEFAULT_DB_BLANK_WRAPPER, Base::DEFAULT_DB_READ_WRAPPER, Base::DEFAULT_DB_WRITE_WRAPPER, Base::DEFAULT_KLASS_MAPPED_SCOPE_WRAPPER, Base::DEFAULT_SCOPE_WRAPPER, Base::DEPENDENCY_DESTROY, Base::DEPENDENCY_DESTROY_IGNORE_REFLECTION_TYPES, Base::DEPENDENCY_NULLIFY, Base::DEPENDENCY_RESTRICT, Base::POLY_KLASS_NAME

Instance Attribute Summary

Attributes inherited from Base

#errors

Instance Method Summary collapse

Methods inherited from Base

#merge_errors, #report_error

Methods included from Utils::Methods

#deep_freeze

Constructor Details

#initialize(class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}) ⇒ Deleter

Returns a new instance of Deleter.



64
65
66
67
68
# File 'lib/bulk_dependency_eraser/deleter.rb', line 64

def initialize class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}
  @class_names_and_ids = class_names_and_ids
  @additional_identifiers_by_id = additional_identifiers_by_id || {}
  super(opts:)
end

Instance Method Details

#executeObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/bulk_dependency_eraser/deleter.rb', line 70

def execute
  if opts_c.verbose && opts_c.enable_invalid_foreign_key_detection
    puts "ActiveRecord::Base.connection.disable_referential_integrity - disabled!"
  end

  current_class_name = 'N/A'
  delete_all_in_db do
    begin
      class_names_and_ids.keys.reverse.each do |class_name|
        current_class_name = class_name
        additional_identifiers = additional_identifiers_by_id[class_name]
        # We have to give implementers the ability to make changes to these lists. Even a full clear.
        # raise "invalid state! #{class_name} not found in 'additional_identifiers_by_id' list" if additional_identifiers.nil?

        # Last in, First out
        ids = class_names_and_ids[class_name].reverse
        klass = constantize(class_name)

        if opts_c.enable_invalid_foreign_key_detection
          # delete with referential integrity
          delete_by_klass_and_ids(klass, ids, additional_identifiers:)
        else
          # delete without referential integrity
          # Disable any ActiveRecord::InvalidForeignKey raised errors.
          # - src: https://stackoverflow.com/questions/41005849/rails-migrations-temporarily-ignore-foreign-key-constraint
          #        https://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractAdapter/disable_referential_integrity
          ActiveRecord::Base.connection.disable_referential_integrity do
            delete_by_klass_and_ids(klass, ids, additional_identifiers:)
          end
        end
      end
    rescue StandardError => e
      raise BulkDependencyEraser::Errors::DeleterError.new(e.class, e.message, deleting_klass_name: current_class_name)
    end
  end

  return errors.none?
end