e51f0bfee7
Account incineration could leave orphaned records due to missing cascade declarations and async jobs failing when the account was already gone. Cascade fixes: - Add Account::Searchable concern to clean up Search::Query (delete_all) and Search::Record (destroy_all, respects SQLite FTS dependent: :destroy) - Add before_destroy in Account::Storage to delete storage entries - Suppress storage entry recording during incineration so attachment purge callbacks don't create entries or enqueue materialize jobs - Guard Access#clean_inaccessible_data_later with unless user.destroyed? to avoid enqueuing pointless jobs during user cascade Job tenancy: - Extract AccountTenanted concern from the global ActiveJob initializer into ApplicationJob (include) with targeted prepends for ActionMailer::MailDeliveryJob and Turbo broadcast jobs - Defer account resolution from deserialize to perform so that missing accounts raise DeserializationError inside the execution path where discard_on can handle it Tests: - Comprehensive incineration test covering 35 model types with before/after assertions and full enqueued job processing - Mailer deliver_later test verifying account context survives job serialization for multi-account users - Turbo broadcast test verifying account-scoped URLs in rendered partials
42 lines
1.3 KiB
Ruby
42 lines
1.3 KiB
Ruby
class Storage::Entry < ApplicationRecord
|
|
belongs_to :account
|
|
belongs_to :board, optional: true
|
|
belongs_to :recordable, polymorphic: true, optional: true
|
|
|
|
scope :pending, ->(last_entry_id) { where.not(id: ..last_entry_id) if last_entry_id }
|
|
|
|
thread_mattr_accessor :recording, default: true
|
|
|
|
def self.suppressing_recording(&block)
|
|
original, self.recording = self.recording, false
|
|
yield
|
|
ensure
|
|
self.recording = original
|
|
end
|
|
|
|
# Records may be destroyed (during cascading deletes) but .id still works.
|
|
# Skip entirely if account is destroyed - no need to track storage for deleted accounts.
|
|
# Skip materialize jobs for destroyed boards since there's nothing to update.
|
|
def self.record(delta:, operation:, account:, board: nil, recordable: nil, blob: nil)
|
|
return if delta.zero?
|
|
return if account.destroyed?
|
|
return unless recording
|
|
|
|
entry = create! \
|
|
account_id: account.id,
|
|
board_id: board&.id,
|
|
recordable_type: recordable&.class&.name,
|
|
recordable_id: recordable&.id,
|
|
blob_id: blob&.id,
|
|
delta: delta,
|
|
operation: operation,
|
|
user_id: Current.user&.id,
|
|
request_id: Current.request_id
|
|
|
|
account.materialize_storage_later
|
|
board&.materialize_storage_later unless board&.destroyed?
|
|
|
|
entry
|
|
end
|
|
end
|