Rails Dragonfly: Keep external image links valid
Many Rails file handling solutions process images when uploading. I like the solution of the Dragonfly Gem to only create image versions when requested. Here a solution how to update the image cache.
The Dragonfly Gem is an image handling gem for RubyOnRails. It is useless without a proxy cache in production, because the gem uses it for storing generated images.
I faced the problem, that the user wanted to link to an image, but another user could edit the image, and it should then update at all linked places. The way dragonfly uses the cache is: Give everything an “Expire” header far in the future, if something changes, it gets a new url. That is a fine solution if you get away with it, because requests will not hit your app, but only the proxy server once an image was generated. But the consequence is, the same url always responses with the same image. You cannot update things and it magically updates everywhere.
As you might know:
There are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karlton
That is, where conditional cache validation comes into play. Conditional cache validation works with “Last-Modified-Since” (sent by you) and “If-Modified-Since” (sent by your browser). A proxy like rack cache falls back to conditional cache validation if the “Exprires” time has past. The disadvantage is: the request hits your app. The advantage is: you can build some logic in your caching mechanism.
class ImagesController < ApplicationController
def show
skip_authorization
im = Image.find(params[:id])
if params[:size] == 'small'
thumb_params = "100x100#"
elsif params[:size] == 'large'
thumb_params = "800x600"
else
thumb_params = "400x300"
end
if stale?(etag: im, last_modified: im.updated_at.utc)
self.response.cache_control[:public] = true
self.response.headers['Expires'] = 10.seconds.from_now.httpdate
self.response.headers['Cache-Control'] = "max-age=10"
send_data im.image.thumb(thumb_params).compress.data, disposition: :inline, type: im.image.mime_type
end
end
end
So what this does: Give the response a short expire time, then revalidate future requests with the Rails stale? method. Only if the image was modified after it was added to your proxy cache, it is generated new.
That way if you have an image url like /images/123-me-eating-cake-jpg and you visit the same url after it has been updated, you will get the right version of the image.
Took me some time to figure it out, hope it saves somebody some time.