In Rails 5 how to use wicked pdf and mailgun together? - ruby-on-rails

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).

Related

PDF from ERB template with PDFKit and Rails

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.

Encoding error using wicked_pdf and rails ActiveJob

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.

Preview emails in rails with text and html

In our Rails 3.2 app we have the following set up to preview emails:
examples_controller
def registration
mail = EventMailer.registration(registration: EventRegistration.last, cache_email: false)
return render_mail(mail)
end
event_mailer.rb
def registration(options = {})
#registration = options[:registration]
#event = Event.find(#registration.event_id)
#subject = options[:subject] || 'Your Learn Registration'
return_email = mail(to: #registration.email, subject: #subject)
# the email if we got it
return_email
end
We just added a text version of the emails, so now in app/views/event_mailer we have registration.html.erb and registration.text.erb. Before adding the text version of the email users could browse to /webmail/examples/registration and view the html version of the email. After adding the text email, that is broken.
Ideas on how to fix this? I tried setting multipart to false in the examples controller, but that did not work.
Also, here is the render_mail method...
def render_mail(mail)
# send it through the inline style processing
inlined_content = InlineStyle.process(mail.body.to_s,ignore_linked_stylesheets: true)
render(:text => inlined_content, :layout => false)
end
I think it's not easy to debug all this code with just seeing some parts, so I'm going to suggest a different solution.
Some time ago I wrote a gem to preview emails (attachments and multiparts as well) in the browser: https://github.com/markets/maily
Main features:
Visual preview via browser (multiparts and attachments)
Template edition (development)
Email delivery
Flexible authorization system
Easy way to hook sample-data for emails
It's a Rails mountable (by default under /maily) engine, so you just need to install it, configure it and you'll be able to preview your email templates in your browser.
You can try to integrate Maily and you'll get a lot of things done, ping me if you have any doubt or suggestion.

How to trigger download with Rails send_data from AJAX post

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'

Prawn PDF with Rails mailer?

I have successfully created an email that sends on creation of a Kase, but now I need to attach a PDF that is created on the fly by Prawn and Prawno. Basically when you visit a kase such as application.com/kase/1 you just append the URL with .pdf i.e. application.com/kase/1.
I spent ages getting the PDF to work and look how I wanted, but I can't figure out how to add the PDF to an auto sending email - mainly because I cannot work out how to give it a link as it's auto generated.
Has anyone ever managed to get this to work?
Thanks,
Danny
I suppose it would be better if you store generated pdf somewhere - for caching purposes, etc.
But with current configuration, you can read generated page with Net::HTTP and attach response:
require 'net/http'
def your_mailer_method(record)
#...
attachment "application/pdf" do |a|
a.body = Net::HTTP.get('yourdomain.com', "/kase/#{record.id}.pdf")
a.filename="your_pdf_name.pdf"
end
end
You really should consider just not using Prawnto, and creating a subclass of Prawn::Document to do what you need. Then, in both your controller and your mailer code, it should just be:
MyReport.new.render
See the Prawn documentation on this:
http://wiki.github.com/sandal/prawn/using-prawn-in-rails
For the newer ones, you dont really need to send a request again, when you can ::
mail.attachments["invoice.pdf"] = {:mime_type => "application/pdf" , :content => pdf_generator}
Instead of doing this ::
send_data pdf.render , :filename => file_name_here , :type => "application/pdf"
just do this ::
pdf.render , :filename => file_name_here , :type => "application/pdf"
Do not send_data, just render that pdf in your email attachment as mentioned in the first snippet.
In fact, i just wrote a Gist on github.
This code works for me
def send_file(file, subject, text, to_email)
#subject = subject
#text = text
attachments["#{invoice.invoice_number}.pdf"] = file
from_email = abc#xyz.com
mail(:to => to_email, :from => from_email, :subject=> subject)
end

Resources