plutonium-core icon indicating copy to clipboard operation
plutonium-core copied to clipboard

Crash in Uppy Component on Validation Errors for New Records

Open smorttey opened this issue 1 month ago • 0 comments

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.rb in render_attachment_preview

Analysis

The crash occurs in the render_attachment_preview method (called via render_existing_attachments).

  1. When validation fails on a new record (e.g., CandidateUpload.new), the controller re-renders the new view.
  2. The form builder attempts to render the Uppy component.
  3. Uppy iterates over field.value to show previews of files the user just uploaded (so they don't lose them).
  4. For a new record, field.value contains ActiveStorage::Attachment objects that are not yet persisted to the database.
  5. The component attempts to generate a hidden input to preserve the file selection:
    input(type: :hidden, ..., value: attachment.signed_id, ...)
    
  6. Calling signed_id on a new/unpersisted record raises ArgumentError from GlobalID, 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:

  1. Safely retrieving the signed_id. If the attachment wrapper is new, we can fallback to the underlying blob.signed_id (which is persisted upon upload).
  2. Rescuing errors to prevent the entire page from crashing if a signed ID cannot be generated.
  3. Explicitly converting filename to 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

smorttey avatar Nov 24 '25 21:11 smorttey