Make sure the image variants we use are preprocessed

to avoid implicitly creating a variant during a GET to a read replica,
which will raise an exception and cause the image to fail to load.

This feels pretty brittle, to be honest, and we'll need to be careful
in the future about creating and using new variants; we will probably
need to backfill the new variant for existing images because we can't
guarantee that they will be created implicitly/lazily if the GET lands
on the replica.

ref: https://fizzy.37signals.com/5986089/collections/7/cards/942
This commit is contained in:
Mike Dalessio
2025-07-18 15:43:31 -04:00
parent ab53523270
commit ef6198d394
4 changed files with 46 additions and 9 deletions
+19
View File
@@ -1,6 +1,25 @@
module Attachments
extend ActiveSupport::Concern
# The variants we use must all declared here so that we can preprocess them.
#
# If they are not preprocessed, then Rails will attempt to transform the image on-the-fly when
# they are first viewed, which may be on the read replica where writing to the database is not
# allowed. Chaos will ensue if that happens.
#
# These variants are patched into ActionText::RichText in config/initializers/action_text.rb
VARIANTS = {
# vipsthumbnail used to create sized image variants has a intent setting to manage colors during
# resize. By setting an invalid intent value the gif-incompatible intent filtering is skipped and
# the gif can be rendered with all its frame intact.
#
# Only `n` is accepted as an override, using the full parameter name `intent` doesnt work.
#
# This was cargo-culted from know-it-all and I imagine it may be fixed at some point.
small: { loader: { n: -1 }, resize_to_limit: [ 800, 600 ] },
large: { loader: { n: -1 }, resize_to_limit: [ 1024, 768 ] }
}
def attachments
rich_text_record&.embeds || []
end
@@ -1,4 +1,4 @@
<% size = local_assigns[:in_gallery] ? [800, 600] : [1024, 768] %>
<% variant = Attachments::VARIANTS[local_assigns[:in_gallery] ? :small : :large] %>
<% width = blob.metadata["width"] %>
<% height = blob.metadata["height"] %>
@@ -14,10 +14,8 @@
<audio controls="true" width="100%" preload="metadata">
<source src="<%= rails_blob_url(blob) %>" type="<%= blob.content_type %>">
</audio>
<% elsif blob.variable? %>
<%= image_tag url_for(blob.variant(loader: { n: -1 }, resize_to_limit: size)), width: width, height: height %>
<% elsif blob.previewable? %>
<%= image_tag url_for(blob.preview(resize_to_limit: size)), width: width, height: height %>
<% elsif blob.variable? || blob.previewable? %>
<%= image_tag url_for(blob.variant(variant)), width: width, height: height %>
<% else %>
<span class="attachment__icon"><%= blob.filename.extension&.downcase.presence || "unknown" %></span>
<% end %>
+4 -4
View File
@@ -1,14 +1,14 @@
<% if eventable&.has_attachments? %>
<span class="event_attachments margin-block-half flex align-center gap-half">
<% eventable.attachments.each do |attachment| %>
<% size = [800, 600] %>
<% variant = Attachments::VARIANTS[:small] %>
<% width = attachment.metadata["width"] %>
<% height = attachment.metadata["height"] %>
<% if attachment.previewable? %>
<%= image_tag url_for(attachment.preview(resize_to_limit: size)), class: "attachment attachment--image", width: width, height: height %>
<%= image_tag url_for(attachment.preview(variant)), class: "attachment attachment--image", width: width, height: height %>
<% elsif attachment.variable? %>
<%= image_tag url_for(attachment.variant(resize_to_limit: size)), class: "attachment attachment--image", width: width, height: height %>
<%= image_tag url_for(attachment.variant(variant)), class: "attachment attachment--image", width: width, height: height %>
<% else %>
<div class="attachment attachment--file attachment--<%= attachment.filename.extension -%>">
<span class="attachment__icon"><%= attachment.filename.extension&.downcase.presence || "unknown" %></span>
@@ -16,4 +16,4 @@
<% end %>
<% end %>
</span>
<% end %>
<% end %>
+20
View File
@@ -0,0 +1,20 @@
module ActionText
module Extensions
module RichText
extend ActiveSupport::Concern
included do
# This overrides the default :embeds association!
has_many_attached :embeds do |attachable|
::Attachments::VARIANTS.each do |variant_name, variant_options|
attachable.variant variant_name, variant_options.merge(preprocessed: true)
end
end
end
end
end
end
ActiveSupport.on_load(:action_text_rich_text) do
include ActionText::Extensions::RichText
end