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.
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-poll
attribute 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.