Generate paperclip missing styles on the fly

Like most developers, who use paperclip gem with their RubyOnRails applications to support picture uploading and generation of distinct styles/thumbnails of pictures, for displaying them in various views, we also do the same in a lot of our apps.

After running one such web app in production for 2 years, the app needed to be redesigned. This required us to generate couple more styles/sizes for each uploaded image.

Paperclip provides a method ‘reprocess!(:style)’ to generate all/one style(s).

Now, when we planned to move this new version of the app to production, we had to generate those missing styles. There were a few thousand thumbnails to be generated, and our estimated time to generate these missing styles was around half an hour. Since we could not have the site available to users without the graphics, we had to put the site in maintenance mode until the new styles got generated. We also realized that as more images get uploaded and used in the system, this downtime if we ever needed to resize graphics again would go up. We could also not do this conversion without taking the site down as more images may get uploaded while we might be resizing using a script.

On brainstorming a bit, we came up with the idea to generate missing styles on demand – this would prevent downtime and also allow the system to be resilient to more styles being added in the future.

This new “On Demand” approach works something like this:

  • If the desired image style is missing, an ‘error’ event will be triggered by img tag.
  • Catch this event and send a request to the app to generate missing style, and return the image url in response.
  • Use this to display the style.

Following the above, missing styles were generated when a user lands on a page with unprocessed style.

Below are the sample code snippets to generate missing styles on demand:

View:

<p>
  <%= image_tag @photo.file.url(:medium), class: 'generate_on_demand', data: {style: :medium, id: @photo.id}%>
  <%= image_tag @photo.file.url(:thumb), class: 'generate_on_demand' , data: {style: :thumb, id: @photo.id}%>
</p>

JS:


    $('img.generate_on_demand').on('error', function() {
        $this = $(this);
        $this.attr('src', imageGeneratorURL($this));
    });

    function imageGeneratorURL(obj) {
        return "/photos/" + obj.data('id') + "/generate?style=" + obj.data('style');
    }

Controller:

  def generate
    @photo.file.reprocess!(params[:style].to_s)
    redirect_to @photo.file.url(params[:style])
  end

Thats it, this worked out well for us.

There are few things to be considered depending upon your use case :

  • Wrap this functionality in some config option, so that you can turn on and off this feature
  • Use exists? method to check if style was generated in between.
  • Before reprocessing the style, you may like to check whether this is a valid style. Else can be abused to generate unwanted styles.
  • Check the referrer to ensure that request’s origin is your own app.
  • Other security features.

Also, one should be mindful of one drawback of this approach. The very first user who lands on a page with missing styles, would need to wait a bit for images to turn up as they get generated in real-time.

This is something we tried and it worked well for us. Do share your thoughts or suggest enhancements to the approach.

Tagged as:

Leave a Reply

Your email address will not be published. Required fields are marked *