How to generate Paperclip attachment thumbnails on demand instead of in advance.
If you have a Rails app that supports image uploads, then you probably use Paperclip. Paperclip codifies an assumption about attachment handling: that you know in advance all of the thumbnail sizes that your web site will need for your image attachments, and that you want to generate those thumbnails when attachments are uploaded.
But what if you need to be able to display thumbnails at any size, specified by request parameters? What if you want to upload image attachments without generating thumbnails, so that you can dynamically resize the images just-in-time (JIT) at any size specified by a user-editable view template?
Resizing thumbnails just in time
Fortunately, Paperclip is flexible enough to handle that kind of scenario. This article demonstrates how to set up dynamic, just-in-time image resizing for Paperclip attachments in this example Rails 3.2.5 app.
First, we start with a basic generated Rails 3.2.5 app. Then we add a scaffold for an Image model. Then we add a Paperclip attachment called “attachment” to the Image model, with support for uploading an image and displaying the uploaded image.
An Active Record model with a Paperclip attachment
You might have an Active Record model or two in your Rails app that supports Paperclip attachments that looks something like the Image model at this point:
1 2 3 4 5 6 7 8 9 10 |
|
Works with S3 or local storage
We’re using S3 for storage in the example, so if you want to run the example then you’ll need to set up an S3 bucket for the example app and set some environment variables before you run the server:
export AWS_ACCESS_KEY_ID='YOUR_ID'
export AWS_SECRET_ACCESS_KEY='YOUR_KEY'
export S3_BUCKET_NAME='paperclip-just-in-time-resizing'
Use a Ruby Proc to add a style to the model instance
At this point, we can add a new Image to the app, and you can see the image on the “show” action. So how do
we set up dynamic thumbnails? We do that by setting
up
the :styles
for the attachment
to be a Ruby Proc, so that it’s evaluated dynamically each time,
by adding:
1
|
|
That Proc references the styles
method on the Image model:
1 2 3 4 5 6 7 |
|
The Image#styles
method normally returns an empty hash, which would mean that only the :original
style would exist. But if there is a @dynamic_style_format
set for this instance of the Image
model, then it will dynamically add a style to the list, with a symbol name derived from URL encoding
the geometry format for the style. So that, for example, the style “150x150>” would result in a style
with the configuration: { :150x150%3E => '150x150>' }
. The method that generates the symbol from
the geometry format string is very simple:
1 2 3 |
|
Resize the attachment thumbnail on demand
Finally, the real work is handled by the Image#dynamic_attachment_url
method, which sets the
current @dynamic_style_format
for the Image instance so that the instance will include the
dynamic style. Then it checks to see if a thumbnail already exists for the specified geometry.
It generates a thumbnail only if necessary, and then it returns a URL for that thumbnail.
1 2 3 4 5 |
|
Use any thumbnail geometry format in your view templates
This method allows you to specify a custom style format in a view template:
<%= image_tag @image.attachment.url %>
<%= image_tag @image.dynamic_attachment_url("150x150>") %>
The second image_tag
, above, uses the Image#dynamic_attachment_url
method to dynamically
generate a thumbnail with a 150 x 150 bounding box. Instead of specifying @image.attachment.url(:original)
or @image.attachment.url(:thumbnail)
or some other
pre-determined thumbnail style, you can specify any style format and the Image model will
generate the thumbnail just-in-time.
The final Active Record model with dynamic thumbnails
Wrapping it all up, the Image model looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Providing a controller action for dynamic thumbnails
The Image#dynamic_attachment_url
method enables you to specify any thumbnail size from inside
of one of your Rails app’s view templates. But what if the images will be embedded on other web
sites? What if you need to be able to provide a URL for an image that includes thumbnail size
parameters? That’s really easy, given what we already have.
First, add a route for the action that you want. In config/routes.rb:
1 2 3 4 5 |
|
Then add a simple controller action that redirects to the URL returned by Image#dynamic_attachment_url
.
1 2 3 4 |
|
Now we can call something like http://localhost:3000/images/1/thumbnail?width=300&height=300
to get an image thumbnail that is resized just in time. The second time that you go to the same
URL, you will see a much faster response because the thumbnail will already be waiting on S3 and
you will be redirected to it immediately.