I am currently building a rails app that uses the jquery-upload plugin to allows users to upload files to do things such as set their user avatar. If the upload is successful, it sets a hidden field on the form with the upload ID, and when the form is submitted, the association is saved. When I manually test this, it works the way it is supposed to. However, I cannot get my RSpec tests to pass.
I am using RSpec as my testing framework and Capybara-webkit as my javascript drive. The field where the file is supposed to attach looks like this
= file_field_tag :file, class: "upload_file_field"
(Also, using slim for templating)
The coffeescript that handles the file upload looks like this
$element.fileupload
dropZone: $dropzoneElement
url: "/uploads.json"
method: "PATCH"
fail: (e, data) =>
#showErrorOnFailure(e, data)
done: (e, data) =>
#onSuccessfulUpload(e, data)
Controller code that handles file uploads looks like this
class UploadsController < ApplicationController
def create
#upload = Upload.new(file: params[:file])
byebug
if #upload.save
respond_to do |format|
format.json { render json: {upload: #upload, url: #upload.file.url(:avatar) } }
end
else
respond_to do |format|
format.json { render json: {}, status: :unprocessable_entity }
end
end
end
end
And here is the RSpec code I am using in the test to attach the file
filepath = "#{Rails.root}/spec/support/fixtures/uploads/flickr.jpg"
attach_file :file, filepath
click_on "Submit"
expect(page).to have_css(".avatar-img img")
When I run the test, the entire request goes through (Capybara does not indicate it had trouble finding the file or the form field). However, the test fails. And when I use byebug to inspect the uploads controller at the point it receives the request to save a new upload, there are no parameters being sent. As in params[:file] evaluates to nil, when it should have the file information for flickr.jpg. Any idea why my file isn't attaching in the test, even though Capybara isn't raising any errors about it.
So after reading through this post I found an answer:
Capybara webkit doesn't pass params from angular
It looks like the problem came from the fact that I was using PATCH to send the uploads. After changing the method to PUT, my tests started passing.
Related
I have a model (let's call it Message) that's using Active Storage with has_many_attached to store multiple uploads through a Javascript routine. I'd like to know the URL of the uploads as soon as these are received and processed by the Update action in my controller. Ideally, the response would be in the form of a JSON string, containing the URL of the file that is just uploaded by the Javascript routine.
Other than having to loop through all the uploads, is there a way to obtain this information as they're uploaded?
I'm not using the DirectUpload javascript suggested in the Active Storage guide, in case you're wondering. The Javascript I'm using sends the files to a custom PATCH action /message/:id/new_upload (where :id is the ID of the Message record), with the BLOB file as its only parameter.
I've tried to use something like this:
def new_upload
#message.uploads.attach(params[:uploads])
respond_to do |format|
format.json {render json: { location: "The URL would go here"} }
end
end
But the attach line at the beginning doesn't return anything (obviously), all it is is doing is saving the attachments. Is there anything I can use to figure out what was just uploaded?
UPDATE
I've managed to make it "work" by doing something like this:
def new_upload
#message.uploads.attach(params[:uploads])
upload_url = rails_blob_url(#message.uploads.blobs.last)
respond_to do |format|
format.json {render json: { location: upload_url} }
end
end
However, this only works for one attachment (the last). Thankfully I only require to upload one file at a time, but I'd prefer to infer the URL by something other than checking the last upload.
I am using wicked-pdf to simplify generating pdf invoices in my Rails 5 application. I have two actions making use of the pdf functionality of wicked-pdf. The first action generates the pdf and renders it in a new browser window. The second method attaches the pdf to an email.
Both of these actions work just fine. The issue comes in when I set my pdf mailer action to 'deliver_later' using ActiveJob/Sidekiq. When I add 'deliver_later' I am presented with a error stating:
"\xFE" from ASCII-8BIT to UTF-8
This error does not happen if I use the "deliver_now" command. Using "deliver_now" send the email and attaches the PDF correctly.
Here is some of my code for the mailing action, mailer and job:
invoices_controller.rb
...
def create
respond_to do |format|
format.html do
pdf = render_to_string( pdf: #order.ruling_invoice,
template: "orders/invoices/show.pdf.haml",
encoding: "utf8",
locals: { order: #order.decorate}
)
SendInvoiceMailJob.new.perform( #order, pdf, #order.token )
redirect_to order_url(id: #order.id, subdomain: current_company.slug), notice: "This invoice has been emailed."
end
end
end
...
send_invoice_mail_job.rb
...
def perform( order, pdf, order_token )
InvoiceMailer.send_invoice(order, pdf, order_token).deliver_later
end
...
invoice_mailer.rb
...
def send_invoice( order, pdf_invoice , invoice_token)
#order = order
attachments["#{invoice_token}.pdf"] = pdf_invoice
mail(
to: #order.email,
from: #order.seller_email
)
end
...
Why would this code work using "deliver_now" in send_invoice_mail_job.rb but it doesn't work using "deliver_later" ?
You can't just throw binary data (the PDF) into job arguments.
https://github.com/mperham/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple
You need to move the PDF compiling from outside of your mailer job to inside it. So in your controller, you can do:
SendInvoiceMailJob.new.perform(#order, #order.token)
Then in your mailer job you can do:
def send_invoice(order, invoice_token)
#order = order
pdf = render_to_string(
pdf: #order.ruling_invoice,
template: "orders/invoices/show.pdf.haml",
locals: { order: #order.decorate }
) )
attachments["#{invoice_token}.pdf"] = pdf_invoice
mail(
to: #order.email,
from: #order.seller_email
)
end
That way you're not passing the PDF binary into the jobs queue.
My application creates customized labels depending on an object's properties (make, model, etc.) and successfully saves them to the server. I'm trying to get it to send_data the image (using to_blob) directly to the browser so the user can immediately open and print the image, instead of saving it to the server first. I'm having a lot of trouble getting the sending to the browser part to work.
I'm using Rails (4.0.0) and rmagick (2.13.4). Please let me know if I can share any other information. Here's the code:
VIEW
<%= link_to fa_icon('qrcode', text: 'Print'),
labels_path(#warranty_item.qr_code, { format: :png }),
method: :post, remote: true, class: 'btn btn-xs btn-warning' %>
CONTROLLER
require 'rmagick'
def create
respond_to do |format|
format.png do
#img = Magick::Image.new(280, 100) { self.background_color = blue' }
#img.format = 'png'
#img_blob = #img.to_blob
send_data #img_blob, filename: 'test_label.png', disposition: 'inline', type: 'image/png'
end
end
end
In Rails' logs, this goes off without a hitch; no errors, no warnings, only a: Sent data test_label.png (0.7ms)
If I run #img.to_blob in the Rails console, it gives me this:
"\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x01 \x00\x00\x00$\x01\x03\x00\x00\x00\x13\xABua\x00\x00\x00\x04gAMA\x00\x00\xB1\x8F\v\xFCa\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xFA\x00\x00\x00\x80\xE8\x00\x00u0\x00\x00\xEA`\x00\x00:\x98\x00\x00\x17p\x9C\xBAQ<\x00\x00\x00\x06PLTE\x00\x00\xFF\xFF\xFF\xFF{\xDC\x99,\x00\x00\x00\x01bKGD\x00\x88\x05\x1DH\x00\x00\x00\x14IDAT8\xCBc`\x18\x05\xA3`\x14\x8C\x82Q#,\x00\x00\x054\x00\x01\xEA\xAF\x9B \x00\x00\x00\x00IEND\xAEB`\x82"
However, if I use the logger, Rails.logger.info #img.to_blob it gives me this:
�PNG
IHDR $�uagAMA��
�a cHRMz&�����u0�`:�p��Q<PLTE����{ܙ,bKGD�HIDAT8�c`�`��Q#,4ꯛ IEND�B`�
Like I'd mentioned above, if I simply save the image, #img.write('test_label.png'), it works perfectly. What am I doing wrong? What am I missing? The code executes, tested in the console it works, but it does not send the image data to the user's browser/downloads file so they can open it. Please help!
It cannot work. Your link with 'remote: true' is an ajax request. You can't download a image to disk from JS. It's a security concern.
read here: enter link description here
I have a page that lets one create records - if the validations aren't satisfied, it redirects to the same page and shows an error message. Here's that snip from the controller:
def create
#signature = Signature.new(signature_params)
if #signature.save
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
end
The trouble is, this is resulting in a full page refresh - so the error message isn't visible because the input form is placed under the fold of the page. I can place it at the top of the page, of course, but is there a way to show the message without reloading the page? Thanks.
OK, so here's what I've settled on:
1) I'm handling validation on the client side with HTML5 "required" attributes - they were created for this explicit purpose and no other gems or plugins are needed. They are supported in all major browsers. Details in this article.
2) I've moved the error messages to the top of the page to handle the case in which a user either is on an old or mobile browser or has JavaScript disabled. Error messages must work with a complete request-response cycle (even if this means re-loading the page) before they work with anything else - this is the unobtrusive JavaScript approach.
3) For the AJAX version, I'm going to be using remote: => true on the form element as explained in the Rails guides. I might be making this open source once I'm done with the callback part of it, and will post a link here.
Obviously, handling errors with flash is the most uniform & DRY way to show the user what's going on, but if you're willing to think outside the box, you'll be able to use Ajax to accomplish a similar job by just handling the errors yourself:
Code Example
#app/controllers/signatures_controller.rb
def create
#signature = Signature.new(signature_params)
if #signature.save
#success = "true"
end
respond_to do |format|
format.js { #errors = error_messages(#signature) }
format.html {
if #success.defined?
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
}
end
end
#app/views/signatures/create.js.erb
<% unless #success.defined? %>
alert(<%=j #errors.inspect() %>)
<% end %>
#app/assets/javascripts/signatures.js
$(document).on("submit", "#signature_form", function() {
$.ajax({
url: "/signatures"
type: "POST"
data: $(this).parent().serialize(); //serialize the form (not the button)
error: function() {
alert("Sorry, there was an error!");
}
});
});
You'd actually be better using JSON for this. If you like the idea, I can refactor it to include JSON for you!
I'm trying to use send_data to return a PNG image as the response for a ajax post request. How do I get the browser to trigger a download on the success callback?
Details
I'm generating a large base64 image using canvas.toDataURL(), and then posting it to Rails (v3.2.6). Rails decodes it to a binary PNG, and sends the image back to the client.
I've also tried send_file but it has the same issue.
Other options
Download image client side: We can't do this because (1) Safari crashes on large base64 URLs, and (2) Safari does not yet support the download attribute on anchor tags which I would need to specify the downloaded image filename.
Use a $.get instead of $.post: We can't do this because we need to send our canvas.toDataURL() with the request to the server. GET requests URIs have size limitations.
create a function in controller
def ajax_download
send_file "path_to_file/" + params[:file]
end
and then in controller action
respond_to do |format|
#java_url = "/home/ajax_download?file=#{file_name}"
format.js {render :partial => "downloadFile"}
end
and make a partial in view folder name with _downloadFile.js.erb and write this line
window.location.href = "<%=#java_url %>"
You can't download a file to disk from JS. It's a security concern. See the blog post below for a good workaround.
http://johnculviner.com/post/2012/03/22/Ajax-like-feature-rich-file-downloads-with-jQuery-File-Download.aspx
Do not just copy and paste the accepted answer. It is a massive security risk that cannot be understated. Although the technique is clever, delivering a file based on a parameter anybody can enter allows access to any file anybody can imagine is lying around.
Here's an example of a more secure way to use the same technique. It assumes there is a user logged in who has an API token, but you should be able to adapt it to your own scenario.
In the action:
current_user.pending_download = file_name
current_user.save!
respond_to do |format|
#java_url = "/ajax_download?token=#{current_user.api_token}"
format.js {render :partial => "downloadFile"}
end
Create a function in the controller
def ajax_download
if params[:token] == current_user.api_token
send_file "path_to_file/" + current_user.pending_download
current_user.pending_download = ''
current_user.save!
else
redirect_to root_path, notice: "Unauthorized"
end
end
Make a partial in view folder name with _downloadFile.js.erb
window.location.href = "<%=#java_url %>"
And of course you will need a route that points to /ajax_download in routes.rb
get 'ajax_download', to: 'controller#ajax_download'