ActiveStorage attachment not ready while Broadcasting

I’ve encountered an issue while trying to broadcast the creation of a new image in our system. The Image model is mind, and it has_one_attached :file. I broadcast its creation via:

after_create_commit do
    broadcast_append_later_to article.online_newsletter, target: ActionView::RecordIdentifier.dom_id(article, :images),
      partial: 'online_newsletters/articles/image', locals: { editable: true }
  end

The partial is approximately:

<%= link_to image.file.variant(resize_to_limit: [2880, 2880]), data: {
  fancybox: dom_id(image.article, :images), caption: image.caption, download_src: url_for(image.file) } do %>
  <%= image_tag image.file.variant(resize_to_fill: [480, 320, crop: :attention]), class: 'is-cropped' %>
<% end %>

Unfortunately, even though the record is committed, the attachment hasn’t uploaded to S3 yet so the request for the image variant becomes a 500 error:

ActiveStorage::FileNotFoundError - ActiveStorage::FileNotFoundError

Is there a way to wait until we know that the image is in S3 before broadcasting the update? I notice that if I update the page with a turbo-stream from the controller to the user that added the image then it works just fine. I’m not sure if this is just a timing coincidence or if the request waits for the image to be in S3 before rendering the view?

Also, given dom_id is so useful in both the controller and model context now, how come it’s not required into those contexts by default? :slight_smile:

Hey @Spike,

It looks like you can check this by utilizing the current active storage service. Here’s an example:

ActiveStorage::Blob.service.exist?(image.file.key)

This worked fine with simple disk storage and it looks like the S3 adapter has an implementation for that method too.

Ref:

Interesting @nickhammond :slight_smile: So I suppose I could wrap that check in a while loop to delay the delivery of the broadcast until the file exists? Probably would need to throttle that though! :smiley:

Bit of a shame that there’s no hook bubbled up to the model when the file has been stored.

@Spike Yah, I was thinking there’d be a stored_at or uploaded_at timestamp in the attachments table but there’s not weirdly, be a great PR though. The other use case though for this method is if that file is removed from the storage service for whatever reason you can check that.

Digging into this deeper, it seems that sometimes Amazon S3 will return a 404 if we ask it for a variant too quickly after the file upload has been processed even with something like this:

  after_create_commit prepend: true do
    if file.blob.service.exist?(file.key)
      broadcast_append_to article.online_newsletter, target: dom_id(article, :images),
        partial: 'online_newsletters/articles/image', locals: { editable: true }
    end
  end

I’ve found that calling .processed on the variant to force it to be created at render time is the way around this issue:

<%= image_tag image.file.variant(resize_to_fill: [480, 320, crop: :attention]).processed, class: 'is-cropped' %>

Using one of the active job broadcast methods would make this less contentious :slight_smile:

I should mention that the prepend: true on after_*_commit is very important if you’re using .processed as otherwise the file won’t exist yet.