135 lines
3.9 KiB
Ruby
135 lines
3.9 KiB
Ruby
class DeleteObjectJob < ApplicationJob
|
|
queue_as :low
|
|
|
|
BATCH_SIZE = 5_000
|
|
INBOX_DEPENDENT_TABLES = %i[
|
|
captain_feedback_logs
|
|
captain_lifecycle_deliveries
|
|
captain_reminders
|
|
captain_reservations
|
|
captain_gallery_items
|
|
captain_inbox_automations
|
|
captain_inbox_reminder_settings
|
|
captain_inboxes
|
|
captain_notification_templates
|
|
captain_pricing_inboxes
|
|
captain_tool_configs
|
|
captain_unit_inboxes
|
|
jasmine_inbox_collections
|
|
jasmine_inbox_settings
|
|
jasmine_tool_configs
|
|
].freeze
|
|
INBOX_NULLIFY_TARGETS = [
|
|
[:captain_conversation_insights, :inbox_id],
|
|
[:captain_pricings, :inbox_id],
|
|
[:jasmine_collections, :owner_inbox_id],
|
|
[:captain_units, :inbox_id],
|
|
[:captain_units, :concierge_inbox_id]
|
|
].freeze
|
|
|
|
def perform(object, user = nil, ip = nil)
|
|
# Pre-purge heavy associations for large objects to avoid
|
|
# timeouts & race conditions due to destroy_async fan-out.
|
|
purge_heavy_associations(object)
|
|
object.destroy!
|
|
process_post_deletion_tasks(object, user, ip)
|
|
end
|
|
|
|
def process_post_deletion_tasks(object, user, ip); end
|
|
|
|
private
|
|
|
|
def heavy_associations
|
|
{
|
|
Account => %i[conversations contacts inboxes reporting_events],
|
|
Inbox => %i[conversations contact_inboxes reporting_events]
|
|
}.freeze
|
|
end
|
|
|
|
def purge_heavy_associations(object)
|
|
purge_inbox_blocking_associations(object) if object.is_a?(Inbox)
|
|
|
|
klass = heavy_associations.keys.find { |k| object.is_a?(k) }
|
|
return unless klass
|
|
|
|
heavy_associations[klass].each do |assoc|
|
|
next unless object.respond_to?(assoc)
|
|
|
|
batch_destroy(object.public_send(assoc))
|
|
end
|
|
end
|
|
|
|
def batch_destroy(relation)
|
|
relation.find_in_batches(batch_size: BATCH_SIZE) do |batch|
|
|
batch.each(&:destroy!)
|
|
end
|
|
end
|
|
|
|
def purge_inbox_blocking_associations(inbox)
|
|
inbox_id = inbox.id
|
|
reservation_ids = select_ids(:captain_reservations, :inbox_id, inbox_id)
|
|
|
|
purge_reservation_children(reservation_ids)
|
|
|
|
# fazer.ai/Captain tables hold hard FKs to inboxes. If these survive, the
|
|
# async delete job fails and the UI shows the inbox again on refresh.
|
|
INBOX_DEPENDENT_TABLES.each { |table| delete_by_column(table, :inbox_id, inbox_id) }
|
|
nullify_inbox_references(inbox_id)
|
|
end
|
|
|
|
def purge_reservation_children(reservation_ids)
|
|
delete_where_in(:captain_lifecycle_deliveries, :captain_reservation_id, reservation_ids)
|
|
delete_where_in(:captain_pix_charges, :reservation_id, reservation_ids)
|
|
end
|
|
|
|
def nullify_inbox_references(inbox_id)
|
|
INBOX_NULLIFY_TARGETS.each do |table, column|
|
|
nullify_by_column(table, column, inbox_id)
|
|
end
|
|
end
|
|
|
|
def select_ids(table, column, value)
|
|
return [] unless column_available?(table, column)
|
|
|
|
sql = "SELECT id FROM #{quote_table(table)} WHERE #{quote_column(column)} = #{Integer(value)}"
|
|
db.select_values(sql).map(&:to_i)
|
|
end
|
|
|
|
def delete_where_in(table, column, values)
|
|
return if values.blank?
|
|
return unless column_available?(table, column)
|
|
|
|
db.execute("DELETE FROM #{quote_table(table)} WHERE #{quote_column(column)} IN (#{values.map { |v| Integer(v) }.join(',')})")
|
|
end
|
|
|
|
def delete_by_column(table, column, value)
|
|
return unless column_available?(table, column)
|
|
|
|
db.execute("DELETE FROM #{quote_table(table)} WHERE #{quote_column(column)} = #{Integer(value)}")
|
|
end
|
|
|
|
def nullify_by_column(table, column, value)
|
|
return unless column_available?(table, column)
|
|
|
|
db.execute("UPDATE #{quote_table(table)} SET #{quote_column(column)} = NULL WHERE #{quote_column(column)} = #{Integer(value)}")
|
|
end
|
|
|
|
def column_available?(table, column)
|
|
db.data_source_exists?(table.to_s) && db.column_exists?(table.to_s, column.to_s)
|
|
end
|
|
|
|
def quote_table(table)
|
|
db.quote_table_name(table)
|
|
end
|
|
|
|
def quote_column(column)
|
|
db.quote_column_name(column)
|
|
end
|
|
|
|
def db
|
|
ActiveRecord::Base.connection
|
|
end
|
|
end
|
|
|
|
DeleteObjectJob.prepend_mod_with('DeleteObjectJob')
|