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.
Related
Using the mailgun API is slightly different from using ActionMailer.
Often I see something like this:
attachment['...'] = ...
in the solutions I found..
but what I am trying to build with mailgun currently looks like this:
def print
#invoice = Invoice.find(params[:id])
#order = #invoice.order
#customer = #order.customer
pdf = render_to_string(:pdf => 'file_name',
:template => 'invoices/invoice.pdf.erb',
:layout => 'invoice_pdf.html')
mg_client = Mailgun::Client.new 'key-xxxxxx'
mb_object = Mailgun::MessageBuilder.new
mb_object.add_attachment pdf
mg_client.send_message 'mg.mydomain.com', mb_object
end
And when I press the "print" button in my application (the pdf is meant to be send to a printer), then I get:
string contains null byte
(highlight!) mb_object.add_attachment pdf
Apparently the pdf object is not really something.
How could this be?
EDIT 1
Maybe it's better to ask, since this also needs to be done: How do I save from wicked pdf to S3 (in as few steps as possible).
In my Rails application I'd like to have a special route to download a custom PDF.
This PDF should be generated via PDFKit from an ERB template in my application. Instead of describing what I'd like to achieve, I better paste some non-executable but commented code:
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render :download_my_list_as_pdf
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
# This is the message I'd like to show at the end
# But using 'render' more than once is not allowed
render plain: 'Download complete'
end
end
I wasn't able to find an answer to this problem yet, any help would be greatly appreciated!
render_to_string(*args, &block)
Raw rendering of a template to a string.
It is similar to render, except that it does not set the response_body
and it should be guaranteed to always return a string.
render does not return a string it sets the response_body of the response.
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render_string(:download_my_list_as_pdf)
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
end
end
However if you are sending a file you cannot send text or html as well. This is not a limitation of Rails but rather how HTTP works. One request - one response.
Usually javascript is used to create notifications surrounding file downloads. But consider first if its really needed as its pretty annoying to users as the browser usually tells you that you downloaded a file anyways.
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.
For rendering a text (not html) message with ActionMailer in Rails 3, I see many threads instructing the programmer to make a .text.erb file and run the following code:
mail do |format|
format.html
format.text
end
...but I wish to render a very short message, without using a mailer view at all. I have success doing this in html format but not in plain text.
I use the following code:
mail do |format|
format.html{ render( text: 'my text' ) }
format.text{ render( text: 'my text' ) }
end
...but it sends an html email every time. What can I do to send a plain-text email and specify its content without a mailer view file?
From doc:
Action Mailer will automatically send multipart emails if you have
different templates for the same action. So, for our UserMailer
example, if you have welcome_email.text.erb and welcome_email.html.erb
in app/views/user_mailer, Action Mailer will automatically send a
multipart email with the HTML and text versions setup as different
parts.
Just do:
mail do |format|
format.text{ render( text: 'my text' ) }
end
If you only want a text email.
I am trying to render a partial inside a JSON file so that I can use it via AJAX. Currently in my JSON file I have:
<% self.formats = ["html"]%>
{
"html": "<%= raw escape_javascript(render(:partial => 'shared/mini_posts', :locals => {:type => "left"}).to_json )%>"
}
This currently returns no response, but my logs show that shared/mini_posts was rendered.
Note that if I try something like:
{
"html" : "test"
}
This returns correctly.
My jQuery looks like:
$j.ajax({
url('/thepage.json'),
dataType: 'json',
success: function(data){
console.log(data);
}
})
In my case, I had the same problem for about a day and a half and after trying lots of combinations of escape_javascript, to_json, render ... content_type, etc I finally achieved what I was looking for, that is rendering a HTML partial in a json response.
So in your controller you have an action like
def index
#candidatos = Candidatos::Base.paginate(page: params[:page], per_page: 3).to_a
respond_to do |format|
format.html # index.html.erb
format.json # index.json.erb
end
end
and if the request had a .json it will use the index.json.erb template, and in my case that template is something like
<% self.formats = ["html"] %>
{
"html":<%= thumbnails_tag(#candidatos).to_json.html_safe %>
}
Note the self.formats = ["html"] is necessary because otherwise the "view" won't find the partial because it will look for a .json partial. and more important, don't use escape_javascript because it will fill the html with \t and \n. In other words, the idea is to pass the output of your partial to .to_json and then to .html_safe.
thumbnails_tag is just a helper I created because I'm using the partial in lots of parts of the app, but basically it has something like
def thumbnails_tag objs
#Uncomment the line below to test when no much data available
# #listas.fill(#listas.first, 0..6)
thumb_span = 4
case #candidatos.length
when 1..3
thumb_span = 12 / #candidatos.length
else
thumb_span = 4
end
thumbs = {span: thumb_span}
render partial: 'candidatos/thumbnails', locals: {candidatos: #candidatos, thumbnail_options: thumbs }
end
Finally, and just as an example, with this approach, in your .js assets you can do something like:
$.get('http://localhost:3000/candidatos.json?page=2', function(d){}, 'json')
.success(function(d){
$('#presidentes div.row-fluid .thumbnails').parent().append(d.html);
})
.error(function(event,error){
console.log(error);
})
No need to gsub for \t and \n in your rails view or JSON.parse string in your Javascript.
You need to change your controller. In the respond_to part you need to add json rendering. Like this:
respond_to do |format|
format.json { render :json => #instancevar }
....
...
..
end
Then you can simply add .json to your url and you will receive the data formated as json. What you might need to do is disabeling the JSON root hich is automaticly added by default. Just add this line to your environment (development.rb, production.rb,...) configuration:
config.active_record.include_root_in_json = false
This is important when you are parsing the json.
You can't call to_json on a partial... it's a method of ActiveRecord. If you want json there, then it should be inside the partial.