Here is what I ended up with to get responsive images with Active Storage. Anyone would have suggestions to clean this up or make it more generic? Would it be worth upstreaming this kind of helper in Active Storage?
# app/models/foo.rb
class Foo < ApplicationRecord
has_one_attached :picture
end
# app/controllers/foos_controller.rb
class FoosController < ApplicationController
def index
@foo = Foo.find(...)
end
end
# app/helpers/foo_helper.rb
class FooHelper
def blob_thumb(blob, limit = [150, 150])
return unless (blob_width, blob_height = blob_size(blob))
width, height = limit
if width < blob_width || height < blob_height
blob.representation(resize_to_limit: limit)
else
blob
end
end
def responsive_image_tag(blob, widths: [640, 1024, 1440, 1920], sizes: "100vw", **options)
return unless analyze_blob(blob)
image_tag(blob, srcset: srcset_for(blob, widths:), sizes:, **options)
end
def srcset_for(blob, widths:)
return unless (blob_width, blob_height = blob_size(blob))
widths
.filter { |width| width <= blob_width }
.tap { |w| w.append(blob_width) if blob_width < widths.max }
.map do |width|
height = (1.0 * width / blob_width * blob_height).to_i
[rails_storage_proxy_url(blob_thumb(blob, [width, height])), "#{width}w"]
end
end
private
def analyze_blob(blob)
if !blob.is_a?(ActiveStorage::Attached) || blob.attached?
blob.tap { |blob| blob.analyze unless blob.analyzed? }
end
end
def blob_size(blob)
analyze_blob(blob)&.metadata&.values_at(:width, :height)
end
end
# app/views/foos/index.html.erb
<%= responsive_image_tag(@foo.picture, alt: @foo.title, class: '...') %>