Rails PaperClip integration with Cloudinary Fetch API in a smarter way

In our application, we have been  using Paperclip with Amazon s3 for image management.  We chose Paperclip as it can integrate with ActiveRecord seamlessly  As it treats files like simple record attributes.

It is used extensively across our products for various use cases. From a volume perspective we serve over 4M image requests per day.

Until 6 months back we were using fixed number of resolutions for the images across our product. However, over time the need arose to generate multiple resolutions at run time. So we have to compromise in page load time as we were loading images with dimensions that can just fit but not perfect.

Replacing paperclip with some other tool would have been too tedious and error prone given our dependency on Paperclip and its widespread usage across the product. So, we started looking for a solution that can solve our problem of dynamic image resizing without making change to our whole application so that we can optimize page load time by using exact image sizes.

After some look around on web we came across  a solution named Cloudinary which is basically a image processing CDN.

What is Cloudinary?

Cloudinary manages your web application’s resources in the cloud. The resources are delivered from high-performance servers through Content Delivery Networks. Resources are uploaded, managed, and transformed using Cloudinary easy to use APIs. Image upload, processing, and delivery are done on Cloudinary servers and automatically scale for handling high load and bursts of traffic.

Cloudinary provides a gem for rails integration but that too requires image_tag replacement with cl_image_tags

So we used a smart way to tackle this which i will explain in detail.

Most of us use paperclip gem with s3 to upload our images in cloud with setting similar to below sample in our model files –

has_attached_file :file,

                :storage => :s3,

                :s3_credentials => #{Rails.root}/config/s3.yml”,

                :s3_host_alias => ”www.example.com”,

                :url => ‘:s3_alias_url’,

                :path => “attachments/:attached_path/:style/:filename”,

                :s3_permissions => ‘public-read’         

To serve images on multiple resolutions/screens paperclip provides a feature of creating styles like

:styles => { :small => “30×15>”, :medium => “155×85>” }

Image can be used with required resolution as

User.avatar_url(:small)

Here come the Cloudinary Fetch API –  

Fetch enables on-the-fly manipulation of existing remote images and optimized delivery via a CDN. Fetched images are cached on your Cloudinary account for performance reasons.

Example

http://res.cloudinary.com/demo/image/fetch/http://upload.wikimedia.org/wikipedia/commons/0/0c/Scarlett_Johansson_Césars_2014.jpg

Questions that arises in using this solution are –

  • How can we change image urls throughout  the site?
  • How can we take advantage of Cloudinary fetch API with minimum effort?

Before answering above question I would like to first explain how paperclip generates the cloud urls.

When you write in your model file

:url => ‘:s3_alias_url’

Then  :s3_alias_url is expanded as

Paperclip.interpolates(:s3_alias_url) do |attachment, style|

#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, “”)}

end

What if we can override the way paperclip generates its urls

For that you can create a initializer file as

initializers/paperclip.rb

Paperclip.interpolates(:s3_alias_url) do |attachment, style|

  image_width = style.to_s.split(“x”)[0].to_i

  image_height = style.to_s.split(“x”)[1].to_i

  selected_style = “,w_#{image_width},h_#{image_height}if image_width*image_height > 0

 #{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias.gsub(‘,:pic_style’,  selected_style)}/#{attachment.path(‘original’).sub(%r{\A/}, “”.freeze)}

end unless Paperclip::Interpolations.respond_to? :s3_alias_url

 

Now, just Update paperclip setting in your model file as

:s3_host_alias => CLOUDINARY_S3_URL

And define this constant in your environment files

development.rb

CLOUDINARY_S3_URL = “res.cloudinary.com/<Cloudinary_Api_key>/image/fetch/:pic_style/https://s3.amazonaws.com/<s3_bucket>

Your are done!!!

Restart your application server and all the uploaded cloud images will be served via cloudinary.

You just changed few lines and Now You are serving thousands of images that you uploaded to s3 bucket directly from Cloudinary in a size of your choice.

How to use images with new setting?

Instead of

@user.avatar_url(:medium)

You can use

@user.avatar_url(250×250)

It will fetch original image from your s3 bucket, resize it on cloudinary to 250px x 250px, and cache it on cloudinary CDN.

Comments