Class: BulkDependencyEraser::Builder

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

Constant Summary collapse

DEFAULT_DB_BUILD_ALL_WRAPPER =
->(builder, block) do
  begin
    block.call
  rescue StandardError => e
    builder.report_error(
      <<~STRING.strip
      Issue attempting to build deletion query for '#{e.building_klass_name}'
        => #{e.original_error_klass.name}: #{e.message}
      STRING
    )
  end
end
DEFAULT_OPTS =
{
  force_destroy_restricted: false,
  verbose: false,
  db_build_all_wrapper: self::DEFAULT_DB_BUILD_ALL_WRAPPER,
  # Some associations scopes take parameters.
  # - We would have to instantiate if we wanted to apply that scope filter.
  instantiate_if_assoc_scope_with_arity: false,
  # wraps around the DB reading
  db_read_wrapper: self::DEFAULT_DB_READ_WRAPPER,
  # Will parse these tables and their dependencies, but will remove the tables from the lists after parsing.
  ignore_tables: [],
  # Won't parse any table in this list
  ignore_tables_and_dependencies: [],
  ignore_klass_names_and_dependencies: [],
  disable_batching: false,
  # Disable model ordering during build batching
  # - Some tables can be too big to order, and just need to trust the DB order
  # - Not 100% guaranteed and could leave orphaned records behind.
  disable_batch_ordering: false,
  # Same as 'disable_batch_ordering', but only for select classes
  disable_batch_ordering_for_klasses: [],
  # a general batching size
  batch_size: 10_000,
  # A specific batching size for this class, overrides the batch_size
  read_batch_size: nil,
  # A specific read batching disable option
  disable_read_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
  reading_proc_scopes: self::DEFAULT_SCOPE_WRAPPER,
  # Applied to reading queries
  # - 1st priority of scopes
  reading_proc_scopes_per_class_name: {},
  # 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

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

Instance Attribute Summary collapse

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(query:, opts: {}) ⇒ Builder

Returns a new instance of Builder.



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
# File 'lib/bulk_dependency_eraser/builder.rb', line 72

def initialize query:, opts: {}
  @query = query
  @deletion_list  = {}
  # CTID column values are stored here
  # -   key: <klass_name (with or without suffixes)>
  # - value: Hash
  #   -   key: Individual record's ID
  #   - value: Hash
  #     -   key: Column Name
  #     - value: plucked column value
  # - these additional identifiers will be used when deleting the klasses
  @deletion_list_with_additional_identifiers = {}
  @nullification_list = {}

  # For any ignored table results, they will be stored here
  @ignore_table_deletion_list = {}
  @ignore_table_nullification_list = {}

  @table_names_to_parsed_klass_names = {}

  super(opts:)

  @ignore_table_name_and_dependencies = opts_c.ignore_tables_and_dependencies.collect { |table_name| table_name }
  @ignore_klass_name_and_dependencies = opts_c.ignore_klass_names_and_dependencies.collect { |klass_name| klass_name }
  @query_schema_parser = BulkDependencyEraser::QuerySchemaParser.new(query:, opts:)
  # Moving pointer, points to the current class that is being queries
  @current_klass_name = query.is_a?(ActiveRecord::Relation) ? query.klass.name : query.name
end

Instance Attribute Details

#current_klass_nameObject (readonly)

Returns the value of attribute current_klass_name.



68
69
70
# File 'lib/bulk_dependency_eraser/builder.rb', line 68

def current_klass_name
  @current_klass_name
end

#deletion_listObject

write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.



65
66
67
# File 'lib/bulk_dependency_eraser/builder.rb', line 65

def deletion_list
  @deletion_list
end

#deletion_list_with_additional_identifiersObject

write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.



65
66
67
# File 'lib/bulk_dependency_eraser/builder.rb', line 65

def deletion_list_with_additional_identifiers
  @deletion_list_with_additional_identifiers
end

#ignore_table_deletion_listObject (readonly)

Returns the value of attribute ignore_table_deletion_list.



66
67
68
# File 'lib/bulk_dependency_eraser/builder.rb', line 66

def ignore_table_deletion_list
  @ignore_table_deletion_list
end

#ignore_table_nullification_listObject (readonly)

Returns the value of attribute ignore_table_nullification_list.



66
67
68
# File 'lib/bulk_dependency_eraser/builder.rb', line 66

def ignore_table_nullification_list
  @ignore_table_nullification_list
end

#nullification_listObject

write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.



65
66
67
# File 'lib/bulk_dependency_eraser/builder.rb', line 65

def nullification_list
  @nullification_list
end

#query_schema_parserObject (readonly)

Returns the value of attribute query_schema_parser.



67
68
69
# File 'lib/bulk_dependency_eraser/builder.rb', line 67

def query_schema_parser
  @query_schema_parser
end

Instance Method Details

#buildObject



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/bulk_dependency_eraser/builder.rb', line 127

def build
  build_all_in_db do
    begin
      if opts_c.verbose
        puts "Starting build for #{@query.is_a?(ActiveRecord::Relation) ? @query.klass.name : @query.name}"
      end

      deletion_query_parser(@query)

      uniqify_errors!

      return errors.none?
    rescue StandardError => e
      raise BulkDependencyEraser::Errors::BuilderError.new(e.class, e.message, building_klass_name: current_klass_name)
    end
  end
end

#executeObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/bulk_dependency_eraser/builder.rb', line 101

def execute
  unless query_schema_parser.execute
    merge_errors(full_schema_parser.errors, 'QuerySchemaParser: ')
    return false
  end

  # go through deletion/nullification lists and remove any tables from 'ignore_tables' option
  build_result = build

  # move any klass names if told to ignore them into their respective new lists
  # - prior approach was to use table_name.classify, but we can't trust that approach.
  opts_c.ignore_tables.each do |table_name|
    table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
      klass = klass_name.constantize
      # Delete klasses from the deletion/nullification lists if they are from ignored tables.
      # - Handles klass name indexes, with and without suffices.
      deletion_index_keys(klass).each do |klass_index|
        ignore_table_deletion_list[klass_index]      = deletion_list.delete(klass_index)      if deletion_list.key?(klass_index)
        ignore_table_nullification_list[klass_index] = nullification_list.delete(klass_index) if nullification_list.key?(klass_index)
      end
    end
  end

  return build_result
end