Modifying ActiveRecord models before preventing deletion

save and destroy are automatically wrapped in a transaction https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

So destroy fails, transactions is rolled back and you can't see updated column in tests.

You could try with after_rollback callback https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_rollback

or do record.destroy check for record.errors, if found update record with method manually record.update_doi if record.errors.any?.

before_destroy :destroyable?
...
def destroyable?
  unless metadata['doi'].blank?
    errors.add('Doi is not empty.')
    throw :abort
  end
end

def update_doi
  modified_metadata = Marshal.load(Marshal.dump(metadata))
  description = "Record does not exist anymore: #{name}. The record with identifier content #{doi} was invalid."
  modified_metadata['description'] = description
  modified_metadata['tombstone'] = true
  update_column :metadata, modified_metadata
end

Tip: use record.reload instead of Record.find(record.id).