plutonium-core
plutonium-core copied to clipboard
Crash in Uppy Component on Validation Errors for New Records
Summary
The Plutonium::UI::Form::Components::Uppy component causes an application crash when a form is re-rendered due to validation errors on a new (unpersisted) record that has a file attached.
Symptoms
- Error:
ActionView::Template::Error (Cannot get a signed_id for a new record) - Cause:
ArgumentError (Cannot get a signed_id for a new record) - Location:
lib/plutonium/ui/form/components/uppy.rbinrender_attachment_preview
Analysis
The crash occurs in the render_attachment_preview method (called via render_existing_attachments).
- When validation fails on a new record (e.g.,
CandidateUpload.new), the controller re-renders thenewview. - The form builder attempts to render the
Uppycomponent. Uppyiterates overfield.valueto show previews of files the user just uploaded (so they don't lose them).- For a new record,
field.valuecontainsActiveStorage::Attachmentobjects that are not yet persisted to the database. - The component attempts to generate a hidden input to preserve the file selection:
input(type: :hidden, ..., value: attachment.signed_id, ...) - Calling
signed_idon a new/unpersisted record raisesArgumentErrorfromGlobalID, causing the crash.
Secondary Issue
Additionally, the title attribute on the preview container is passed an ActiveStorage::Filename object instead of a string, which causes a Phlex::ArgumentError in stricter environments or newer Phlex versions.
title: attachment.filename # Returns object, expects String
Workaround / Fix
The fix involves:
- Safely retrieving the
signed_id. If the attachment wrapper is new, we can fallback to the underlyingblob.signed_id(which is persisted upon upload). - Rescuing errors to prevent the entire page from crashing if a signed ID cannot be generated.
- Explicitly converting
filenameto a string.
Monkey Patch (config/initializers/plutonium_uppy_patch.rb)
# frozen_string_literal: true
# Monkey patch to fix crashes in Plutonium Uppy component
# 1. Fixes Phlex::ArgumentError by calling .to_s on filename
# 2. Fixes ArgumentError (GlobalID) by safely handling signed_id for new records
ActiveSupport.on_load(:action_controller) do
require "plutonium/ui/form/components/uppy"
Plutonium::UI::Form::Components::Uppy.class_eval do
def render_attachment_preview(attachment)
input_name = if attributes[:multiple]
"#{attributes[:name].sub(/\[\]$/, "")}[]"
else
attributes[:name]
end
div(
class: "attachment-preview group relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-all duration-300",
data: {
controller: "attachment-preview",
attachment_preview_mime_type_value: attachment.content_type,
attachment_preview_thumbnail_url_value: attachment_thumbnail_url(attachment),
attachment_preview_target: "thumbnail"
},
# FIX 1: Convert filename object to string
title: attachment.filename.to_s
) do
# FIX 2: Handle new records where signed_id might fail (e.g. validation errors on new upload)
# We try to get the blob's signed_id first (which exists for uploaded files), then the attachment's.
# We wrap in rescue to be safe.
sid = (attachment.try(:blob)&.signed_id rescue nil) || (attachment.signed_id rescue nil)
if sid
input(type: :hidden, name: input_name, multiple: attributes[:multiple], value: sid, autocomplete: "off", hidden: true)
end
render_preview_content(attachment)
render_filename(attachment)
render_delete_button
end
end
def render_filename(attachment)
div(
class: "px-2 py-1.5 text-sm text-gray-700 dark:text-gray-300 border-t border-gray-200 dark:border-gray-700 truncate text-center bg-white dark:bg-gray-800",
# FIX 1: Convert filename object to string
title: attachment.filename.to_s
) do
plain attachment.filename.to_s
end
end
end
end