Search code examples
ruby-on-railsamazon-s3carrierwavesidekiq

Display image uploaded to S3 with Carrierwave/Sidekiq after form submit


I'm using Sidekiq with CarrierWave and CarrierWaveBackgrounder to upload my images to S3 in the background. This works fine except for one thing: after I submit my form, the image gets enqueued in Sidekiq and processed, but the page reload happens faster than the background job, resulting in a 404 on the uploaded image. After another page refresh, the image usually shows up.

I'm wondering if there's a way to either show a tmp file (I have a image_tmp column in my database which seems to store a path to the file while it's processing/uploading), or reload my image after the processing is done.

I could poll my database for 'image_processing' to change to true, but that seems a bit like a waste of requests.

Relevant parts of my User class:

mount_uploader :profile_image, ProfileImageUploader

process_in_background :profile_image
store_in_background :profile_image

ProfileImageUploader includes Backgrounder

class ProfileImageUploader < CarrierWave::Uploader::Base

  include ::CarrierWave::Backgrounder::Delay
  include CarrierWave::MiniMagick

My db has fields for profile_image_processing and profile_image_tmp, as well as, of course, a profile_image column.


Solution

  • I have solved this by polling for the image_processing attribute. My image upload partial looks like this:

    .form-group{ :class => (f.error field) ? "has-feedback has-error" : nil }
      = f.label field, :class => "col-md-3 control-label"
    
      .col-md-9
        = f.label field do
          = image_tag('ajax-loader.gif', class: 'image-loader hidden')
          - unless resource.image.blank?
            %p= image_tag(resource.image.thumb.url, data: { poll: resource.image_processing })
          - else
            %p No image uploaded yet.
    
          = f.input_field field, :as => :file
        %span.help-inline
          = f.error field, :class => "help-inline"
    

    I then hook into the data-pollattribute with the following CoffeeScript class:

    $ ->
    
      class ImagePoller
    
        constructor: (@$el) ->
          @$loader      = @$el.prev('.image-loader')
          @timer        = null
          @processing   = JSON.parse @$el.attr('data-poll')
    
          @resourcePath = "#{window.location.href}.json"
    
          @startPolling() if @processing
    
        startPolling: =>
          if @processing
            @toggleLoader()
            @$el.hide()
            @timer = setInterval =>
              promise = $.getJSON @resourcePath
              promise.done (data) =>
                @processing = data.image_processing
                @fetchImage(data.image.thumb.url) if not @processing
            , 100
    
        fetchImage: (image) ->
          clearInterval @timer
          @toggleLoader()
          @$el.attr 'src', image
          @$el.show()
    
        toggleLoader: ->
          @$loader.toggle()
    
      $('[data-poll]').each ->
        new ImagePoller $(@)
    

    Seems to work fine for my purposes.