ruby-on-railsherokupaperclipunicornjquery-upload-file-plugin

Asynchronous jQuery Upload Still Seems to Upload Something to Heroku


I have a fairly simple Rails app running on Heroku where end users can upload two photos and receive a link to the photo via email and SMS.

The app was originally running Paperclip in real-time and timing out sometimes because of Heroku's 30-second limit. I switched to jQuery Upload and S3 to upload the images prior to hitting submit to create the customer, with Paperclip as a delayed job after the fact. This works perfectly -- except it continues to time out upon #create, and when I look in the lower left of my browser it says "Uploading (XX%)" as though it is uploading the already-uploaded files upon #create. I am pulling my hair out.

Again, the photos load fine to S3. On a fast connection, this does not time out, and everything works fine. On a slower connection, it times out upon hitting Submit on the form (after images are uploaded successfully).

Code below. Very appreciative if anyone sees what I am missing - why would this form be trying to submit the photos again?

#Controller

def create
    @s3_direct_post = S3_BUCKET.presigned_post(key: "photos/#{SecureRandom.uuid}/${filename}", success_action_status: 201, acl: :public_read)

if params[:customer][:sms]
  params[:customer][:sms].gsub!(/[\s\-\(\)]+/, '')
end

@customer = current_salesperson.customers.build(customer_params)

if @customer.direct_upload_photo2_url?
  @customer.turkee_status = "ready_to_send"
end

respond_to do |format|
  if @customer.save
    @customer.queue_processing

    unless @customer.email.blank?
      CustomerMailer.delay.welcome_email(@customer)
    end

    unless @customer.sms.blank?
      send_sms
    end

    if params[:fbemail]
      CustomerMailer.post_to_facebook(@customer).deliver
    end
    format.html { redirect_to @customer, notice: 'Customer was successfully created.' }
    format.json { render action: 'show', status: :created, location: @customer }
  else
    format.html { render action: 'new' }
    format.json { render json: @customer.errors, status: :unprocessable_entity }
      end
    end
  end

Relevant Model

class Customer < ActiveRecord::Base

  has_attached_file :photo,
          :styles => { :thumb => "228x152#" },
          :storage => :s3,
          :s3_credentials => {
            :bucket => ENV['S3_BUCKET_NAME'],
            :access_key_id => ENV['AWS_ACCESS_KEY_ID'],
            :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
          },
          :url =>  ":s3_domain_url",
          :path => "photos/:owner_id/:id:hash:style.:extension",
          :hash_secret => "XXX",
          :region => "us-west-2",
          :s3_host_name => "s3.amazonaws.com",
          :default_url => "https://s3-us-west-2.amazonaws.com/missing.jpg"

def queue_processing
        Customer.delay.send_to_paperclip(id)
      end

Form:

<div class="form-group"> <%= form_for(@customer, :html => { class: "directUpload" }) do |f| %>   <% if @customer.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@customer.errors.count, "error") %> prohibited this customer from being saved:</h2>

          <% @customer.errors.full_messages.each do |msg| %>
            <%= msg %><br />
          <% end %>
    </div>   <% end %>

  <div class="form-group">
    <%= f.label :name, "Customer Name" %>
    <%= f.text_field :name, class: 'form-control', :disabled => @disabled %>   </div>   <div class="form-group">
    <%= f.label :email %>
    <%= f.text_field :email, class: 'form-control' %>   </div>

    <div class="form-group">
    <%= f.label :sms, 'Cell Phone # (for one-time SMS)' %>
    <%= f.text_field :sms, class: 'form-control' %>
    </div>


  <span>   <div class="form-group">   <%= f.label :direct_upload_photo2_url, 'Photograph the Second Part' %>   <%= f.file_field :direct_upload_photo2_url, class: 'form-control' %>   </div>   </span>


<script> $(function() {   $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('button[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
    fileInput.fileupload({
      fileInput:       fileInput,
      url:             '<%= @s3_direct_post.url %>',
      type:            'POST',
      autoUpload:       true,
      formData:         <%= @s3_direct_post.fields.to_json.html_safe %>,
      paramName:        'file', 
      dataType:         'XML',  
      replaceFileInput: false,
      progressall: function (e, data) {
        var progress = parseInt(data.loaded / data.total * 100, 10);
        progressBar.css('width', progress + '%')
      },
      start: function (e) {
        submitButton.prop('disabled', true);

        progressBar.
          css('background', 'green').
          css('display', 'block').
          css('width', '0%').
          text("Loading...");
      },
      done: function(e, data) {
        submitButton.prop('disabled', false);
        progressBar.text("Uploading done");

        // extract key and generate URL from response
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = '//<%= @s3_direct_post.url.host %>/' + key;

        // create hidden field
        var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
        form.append(input);
      },
      fail: function(e, data) {
        submitButton.prop('disabled', false);

        progressBar.
          css("background", "red").
          text("Failed");
      }
    });   }); }); </script>

  <div class="form-group">   <%= f.label :direct_upload_photo_url, 'Take or Choose a LANDSCAPE Photo' %>   <%= f.file_field :direct_upload_photo_url, class: 'form-control' %>   </div>


    <div class="form-group">
    <span class="btn btn-default btn-file">
      <input id="fbemail" name="fbemail" type="checkbox" value="1"> Post to Facebook
    </span>
    </div>
     <div class="actions">
    <%= f.button "Submit", class: 'btn btn-primary' %>   </div>


<% end %> </div>

The error in the logs is:

    Jan 29 06:14:20 myname app/web.1: Started POST "/customers" for 70.192.215.71 at 2015-01-29 14:14:19 +0000 
Jan 29 06:14:20 myname app/web.1:   Parameters: {"utf8"=>"✓", "authenticity_token"=>"ypEWqX54OUBUGTm4GNtfM1QjSnqxZHB4bFcKw==", "customer"=>{"name"=>"John ", "email"=>"XXX@gmail.com", "sms"=>"206XXXXXXX", "direct_upload_photo2_url"=>"//myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg", "direct_upload_photo_url"=>"//myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg", "second_salesperson_id"=>"", "owner_id"=>"26"}, "button"=>""} 
Jan 29 06:14:20 myname app/web.1: Processing by CustomersController#create as HTML 
Jan 29 06:14:20 myname app/web.1:   Salesperson Load (1.9ms)  SELECT  "salespeople".* FROM "salespeople" WHERE "salespeople"."id" = $1  ORDER BY "salespeople"."id" ASC LIMIT 1  [["id", 39]] 
Jan 29 06:14:20 myname app/web.1:    (2.2ms)  BEGIN 
Jan 29 06:14:20 myname app/web.1:   SQL (3.7ms)  UPDATE "salespeople" SET "customers_count" = COALESCE("customers_count", 0) + 1 WHERE "salespeople"."id" = $1  [["id", 39]]  
Jan 29 06:14:20 myname app/web.1:    (0.7ms)  BEGIN 
Jan 29 06:14:20 myname app/web.1:    (2.7ms)  COMMIT 
Jan 29 06:14:20 myname app/web.1:   SQL (6.0ms)  INSERT INTO "customers" ("name", "email", "owner_id", "sms", "direct_upload_photo_url", "direct_upload_photo2_url", "salesperson_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id"  [["name", "John "], ["email", "XXX@gmail.com"], ["owner_id", 26], ["sms", "206XXXXXXX"], ["direct_upload_photo_url", "//myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg"], ["direct_upload_photo2_url", "//myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg"], ["salesperson_id", 39], ["created_at", "2015-01-29 14:14:20.443471"], ["updated_at", "2015-01-29 14:14:20.443471"]] 
Jan 29 06:14:20 myname app/worker.1:   Delayed::Backend::ActiveRecord::Job Load (2.7ms)  UPDATE "delayed_jobs" SET locked_at = '2015-01-29 14:14:20.518245', locked_by = 'host:b4e4f686-7966-4131-9609-61b493f6ca44 pid:3' WHERE id IN (SELECT  "delayed_jobs"."id" FROM "delayed_jobs" WHERE ((run_at <= '2015-01-29 14:14:20.517036' AND (locked_at IS NULL OR locked_at < '2015-01-29 10:14:20.517201') OR locked_by = 'host:b4e4f686-7966-4131-9609-61b493f6ca44 pid:3') AND failed_at IS NULL)  ORDER BY priority ASC, run_at ASC LIMIT 1 FOR UPDATE) RETURNING * 
Jan 29 06:14:20 myname app/web.1:    (2.8ms)  COMMIT 
Jan 29 06:14:20 myname app/web.1:   SQL (1.2ms)  INSERT INTO "delayed_jobs" ("handler", "run_at", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["handler", "--- !ruby/object:Delayed::PerformableMethod\nobject: !ruby/class 'customer'\nmethod_name: :send_to_paperclip\nargs:\n- 111\n"], ["run_at", "2015-01-29 14:14:20.521860"], ["created_at", "2015-01-29 14:14:20.522421"], ["updated_at", "2015-01-29 14:14:20.522421"]] 
Jan 29 06:14:20 myname app/web.1:    (0.7ms)  BEGIN 
Jan 29 06:14:20 myname app/web.1:    (3.2ms)  COMMIT 
Jan 29 06:14:20 myname app/web.1:   SQL (3.0ms)  INSERT INTO "delayed_jobs" ("handler", "run_at", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["handler", "--- !ruby/object:Delayed::PerformableMailer\nobject: !ruby/class 'CustomerMailer'\nmethod_name: :welcome_email\nargs:\n- !ruby/object:customer\n  raw_attributes:\n    id: '111'\n    name: 'John '\n    email: XXXX@gmail.com\n    created_at: 2015-01-29 14:14:20.443471733 Z\n    updated_at: 2015-01-29 14:14:20.443471733 Z\n    salesperson_id: 39\n    photo_file_name: \n    photo_content_type: \n    photo_file_size: \n    photo_updated_at: \n    views: '0'\n    owner_id: '26'\n    photo26: \n    tweet_count: '0'\n    facebook_count: '0'\n    photo2_file_name: \n    photo2_content_type: \n    photo2_file_size: \n    photo2_updated_at: \n    second_salesperson_id: ''\n    turkee_task_id: \n    turkee_status: not_sent\n    make: \n    model: \n    year: \n    sms: '206XXXXXXX'\n    direct_upload_photo_url: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n    direct_upload_photo2_url: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n  attributes: !ruby/object:ActiveRecord::AttributeSet\n    attributes: !ruby/object:ActiveRecord::LazyAttributeHash\n      types:\n        id: &3 !ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Integer\n          precision: \n          scale: \n          limit: \n          range: !ruby/range\n            begin: -2147483648\n            end: 2147483648\n            excl: true\n        name: &1 !ruby/object:ActiveRecord::Type::String\n          precision: \n          scale: \n          limit: 255\n        email: *1\n        created_at: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n          subtype: &2 !ruby/object:ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime\n            precision: \n            scale: \n            limit: \n        updated_at: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n          subtype: *2\n        salesperson_id: *3\n        photo_file_name: *1\n        photo_content_type: *1\n        photo_file_size: *3\n        photo_updated_at: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n          subtype: *2\n        views: *3\n        owner_id: *3\n        photo26: *1\n        tweet_count: *3\n        facebook_count: *3\n        photo2_file_name: *1\n        photo2_content_type: *1\n        photo2_file_size: *3\n        photo2_updated_at: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n          subtype: *2\n        second_salesperson_id: *3\n        turkee_task_id: *3\n        turkee_status: *1\n        make: *1\n        model: *1\n        year: *1\n        sms: &4 !ruby/object:ActiveRecord::Type::String\n          precision: \n          scale: \n          limit: \n        direct_upload_photo_url: *4\n        direct_upload_photo2_url: *4\n      values:\n        id: \n        name: \n        email: \n        created_at: \n        updated_at: \n        salesperson_id: \n        photo_file_name: \n        photo_content_type: \n        photo_file_size: \n        photo_updated_at: \n        views: '0'\n        owner_id: \n        photo26: \n        tweet_count: '0'\n        facebook_count: '0'\n        photo2_file_name: \n        photo2_content_type: \n        photo2_file_size: \n        photo2_updated_at: \n        second_salesperson_id: \n        turkee_task_id: \n        turkee_status: not_sent\n        make: \n        model: \n        year: \n        sms: \n        direct_upload_photo_url: \n        direct_upload_photo2_url: \n      additional_types: {}\n      materialized: true\n      delegate_hash:\n        id: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: id\n          value_before_type_cast: '111'\n          type: *3\n          value: 111\n        name: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: name\n          value_before_type_cast: 'John '\n          type: *1\n          value: 'John '\n        email: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: email\n          value_before_type_cast: XXX@gmail.com\n          type: *1\n          value: XXX@gmail.com\n        created_at: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: created_at\n          value_before_type_cast: 2015-01-29 14:14:20.443471733 Z\n          type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n            subtype: *2\n          value: 2015-01-29 14:14:20.443471733 Z\n        updated_at: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: updated_at\n          value_before_type_cast: 2015-01-29 14:14:20.443471733 Z\n          type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n            subtype: *2\n          value: 2015-01-29 14:14:20.443471733 Z\n        salesperson_id: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: salesperson_id\n          value_before_type_cast: 39\n          type: *3\n          value: 39\n        photo_file_name: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo_file_name\n          value_before_type_cast: \n          type: *1\n          value: \n        photo_content_type: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo_content_type\n          value_before_type_cast: \n          type: *1\n          value: \n        photo_file_size: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo_file_size\n          value_before_type_cast: \n          type: *3\n          value: \n        photo_updated_at: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo_updated_at\n          value_before_type_cast: \n          type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n            subtype: *2\n          value: \n        views: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: views\n          value_before_type_cast: '0'\n          type: *3\n          value: 0\n        owner_id: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: owner_id\n          value_before_type_cast: '26'\n          type: *3\n          value: 26\n        photo26: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo26\n          value_before_type_cast: \n          type: *1\n          value: \n        tweet_count: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: tweet_count\n          value_before_type_cast: '0'\n          type: *3\n          value: 0\n        facebook_count: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: facebook_count\n          value_before_type_cast: '0'\n          type: *3\n          value: 0\n        photo2_file_name: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo2_file_name\n          value_before_type_cast: \n          type: *1\n          value: \n        photo2_content_type: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo2_content_type\n          value_before_type_cast: \n          type: *1\n          value: \n        photo2_file_size: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo2_file_size\n          value_before_type_cast: \n          type: *3\n          value: \n        photo2_updated_at: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: photo2_updated_at\n          value_before_type_cast: \n          type: !ruby/object:ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter\n            subtype: *2\n          value: \n        second_salesperson_id: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: second_salesperson_id\n          value_before_type_cast: ''\n          type: *3\n          value: \n        turkee_task_id: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: turkee_task_id\n          value_before_type_cast: \n          type: *3\n          value: \n        turkee_status: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: turkee_status\n          value_before_type_cast: not_sent\n          type: *1\n          value: not_sent\n        make: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: make\n          value_before_type_cast: \n          type: *1\n          value: \n        model: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: model\n          value_before_type_cast: \n          type: *1\n          value: \n        year: !ruby/object:ActiveRecord::Attribute::FromDatabase\n          name: year\n          value_before_type_cast: \n          type: *1\n          value: \n        sms: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: sms\n          value_before_type_cast: '206XXXXXXX'\n          type: *4\n          value: '206XXXXXXX'\n        direct_upload_photo_url: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: direct_upload_photo_url\n          value_before_type_cast: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n          type: *4\n          value: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n        direct_upload_photo2_url: !ruby/object:ActiveRecord::Attribute::FromUser\n          name: direct_upload_photo2_url\n          value_before_type_cast: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n          type: *4\n          value: //myname.s3.amazonaws.com/photos/380057ca-f611-4541-80a1-6850560de612/image.jpg\n  new_record: false\n"], ["run_at", "2015-01-29 14:14:20.551569"], ["created_at", "2015-01-29 14:14:20.551905"], ["updated_at", "2015-01-29 14:14:20.551905"]] 
Jan 29 06:14:20 myname app/web.1:   owner Load (2.4ms)  SELECT  "owners".* FROM "owners" WHERE "owners"."id" = $1 LIMIT 1  [["id", 26]] 
Jan 29 06:14:21 myname app/web.1: SMS: To: +1206XXXXXXX Body: "Check out your photo at http://url/name/111" 
Jan 29 06:14:21 myname app/web.1: E, [2015-01-29T14:14:20.779161 #3] ERROR -- : worker=0 PID:6 timeout (30s > 29s), killing 
Jan 29 06:14:21 myname app/web.1: E, [2015-01-29T14:14:20.808642 #3] ERROR -- : reaped #<Process::Status: pid 6 SIGKILL (signal 9)> worker=0 
Jan 29 06:14:21 myname heroku/router: at=error code=H13 desc="Connection closed without response" method=POST path="/customers" host=www.url.com request_id=e02c7966-0e1c-44a6-b624-72c882683507 fwd="70.192.215.71" dyno=web.1 connect=1ms service=29974ms status=503 bytes=0 

Solution

  • I ended up moving the jQuery Upload into its own separate form above the Rails create form. This fixed it.