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
I ended up moving the jQuery Upload into its own separate form above the Rails create form. This fixed it.