79194255

Date: 2024-11-15 23:53:15
Score: 0.5
Natty:
Report link

Ok, I've struggled my way though this for almost 2 days now, and finally have something I'm sort-of happy with. There's still room for improvement. Eventually I got it sorted using Vips with some tip offs from this github conversation and this GoRails thread on saving variants in a job.

Model:

class Company < ApplicationRecord # :nodoc:
  has_one_attached :logo do |attachable|
    attachable.variant :medium, resize_to_fit: [300, nil]
    attachable.variant :large, resize_to_fit: [700, nil]
  end

  after_save :process_logo_if_changed

  private
    def process_logo_if_changed 
      ImagePreprocessJob.perform_later(logo.id) if logo.blob&.saved_changes?
    end
end

Job:

class ImagePreprocessJob < ApplicationJob
  queue_as :latency_5m

  def perform(attachment_id)
    attachment = ActiveStorage::Attachment.find(attachment_id)
    raise "Attachment is not an image" unless attachment&.image?

    # svg and jfif will break Vips variants, convert to png
    if attachment.content_type == "image/svg+xml" || jfif?(attachment.blob)
      convert_to_png(attachment)
      attachment = attachment.record.send(attachment.name) # switch to new png attachment
    end

    raise "Attachment ID: #{attachment.id} is not representable" unless attachment.representable?

    # save variants
    attachment.representation(resize_to_fit: [300, nil]).processed # medium size
    attachment.representation(resize_to_fit: [700, nil]).processed # large size
  end

  def convert_to_png(attachment)
    filename = attachment.filename.to_s.rpartition(".").first # remove existing extension

    png = Vips::Image.new_from_buffer(attachment.blob.download, "")

    attachment.purge
    attachment.record.send(attachment.name).attach(
      io: StringIO.new(png.write_to_buffer(".png")),
      filename: "#{filename}.png",
      content_type: "image/png"
    )
  end

  def jfif?(blob)
    file_content = blob.download
    return (file_content[6..9] == "JFIF")
  end
end

I played with preprocessed: true in the model as described in the Active Storage Guide, but it would fill the log up with errors as it tries to create variants on invariable svg files before the job runs. So I just moved the processing/saving of variants into the job.

I was not able to solve this using the image_processing gem despite trying several ways. On the whole it was still far more difficult and a more convoluted solution than I expected - I won't mark this as the answer for quite a while as I'd love to see a more elegant and streamlined implementation, and I'm open to suggestions on how this could be improved.

Reasons:
  • Blacklisted phrase (1): days now
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: NGobin