Creating pdf and sending it via email - ruby-on-rails

I am working on a phone billing application.
I have created simple Rails App with 3 tables Client, Invoice, CallRecord.
I can browse my dummy data and all works fine.
I would like to add a feature to send invoices to clients via email.
I was thinking about creating a simple link/button on the invoice#show view: Send invoice.
I was looking at WickedPDF gem as it will generate pdf from HTML.
I have three questions:
1) Is WickedPDF good for what I am looking for?
I am not trying to serve PDF to a user browsing the website. I don't want to make it downloadable.
I just need to generate it and save it when the Send Invoice is clicked, if it hasn't been generated already. And then email it using rails mailer to a client keeping the generated copy on the server(all this will be done in a custom controller action).
2) Where would be the best place to store this generated pdf's? Custom folder in Assets folder, public folder?
3)How would you go about saving files using rails? I have done a bit of rails but I have never done anything this direction.

You can actually use Wicked-PDF directly without the
controllers by:
pdf = WickedPdf.new.pdf_from_string('<h1>Hello There!</h1>')
or
WickedPdf.new.pdf_from_string(
render_to_string('templates/pdf.html.erb', :layout => 'pdfs/layout_pdf'),
:footer => {
:content => render_to_string(:layout => 'pdfs/layout_pdf')
}
) # which is awesome
Storing them should be in the public folder in a similar format:
public/users/:user_id/:invoice_id
you should consider doing that as a delayed job using sidekiq or something because the generation would take time and the user should not wait until it is generated.

Related

Attach Prawn pdf to email

I have been searching for ages and I still cant seem to figure this out,
I am currently using the prawn gem and I want to be able to attach a pdf to an email in my invoice controllers create action. I am currently able to link to a generated pdf from my invoices/show page through http://localhost:3000/invoices/297.pdf but I cant figure out how to attach this pdf to an email.
Currently I am not storing the generated PDF anywhere and my mailer looks like this
Mailer
class InvoiceMailer < ActionMailer::Base
default :from => "notifications#example.com"
def invoice_email(user)
#user = user
mail(:to => user.email, :subject => "Invoice Recieved")
end
end
And my InvoicesController Create Action
{...}
respond_to do |format|
if #invoice.save
InvoiceMailer.invoice_email(#invoice.user).deliver
format.html { redirect_to invoice_url(#invoice, back_path:
{...}
How can I add my invoice as an attachment to this mailer? Do I need to store the invoice somewhere before I can send it?
How you do this depends on how long the PDF generation takes and/or how much load it places on your server and whether you care about that. In my case I was generating PDFs from user-generated-content and I was seeing some PDF creation times up in the 30+ seconds range. Solving for that becomes a run-the-job-somewhere-else and cache-it (whether DB or cloud storage) issue.
#toddmetheny is quite right in suggesting cloud storage for all but the simplest solutions. It gets more interesting if you are hosting on something like Heroku with ephemeral storage, or if you are separating PDF creation from email sending from user requests (e.g. from Heroku again, web dynos vs worker dynos). You can generate the PDF to a local temporary file, but that temporary file may not be there by the time you need to read it in your Mailer running on a 'worker'.
Really simple option
In your Mailer you could generate the PDF to a local file, read it back into memory, then attach it:
def invoice_email(user)
#user = user
attachments['filename_for_user.pdf'] = generate_pdf_content
mail(:to => user.email, :subject => "Invoice Recieved")
end
private
# I had troubles trying to get Prawn to generate 'in memory'
# so just write to, then read, then unlink a tempfile
def generate_pdf_content
pdf = some_method_that_generates_a_prawn_pdf_object
Tempfile.create do |f|
pdf.render_file f
f.flush
File.read(f)
end
end
I suggest you start here to get things working.
More complicated option
Someday you may want to separate the job that generates the PDF (which can take a long time) from the jobs that send email, which are usually much faster. The way I do this is to have a job that generates the PDF to a local Tempfile, then uploads that file to S3 storage and records the S3 object id (I do it in my database, you could just do it as a job attribute you push around).
When complete, that job creates a new mailer job. The mailer job (which may execute on a different server) downloads the PDF from S3 storage to a local file, then adds it to the email much like the simple option above.
You'll need a url you can work with. Any cloud storage solution is an option if you don't want to store it on your database.
Here's some pertinent info on adding attachments to mailers from rails guides on action mailer:
2.3.1 Adding Attachments
Action Mailer makes it very easy to add attachments.
Pass the file name and content and Action Mailer and the Mail gem will automatically guess the mime_type, set the encoding and create the attachment.
attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
When the mail method will be triggered, it will send a multipart email with an attachment, properly nested with the top level being multipart/mixed and the first part being a multipart/alternative containing the plain text and HTML email messages.

How to keep file field value when validation failed

I have a classic rails 3 form with a file field, everything works fine, upload works and data is saved to database.
When a validation failed, for example, title is missing, then the user is sent back to the form with a render :action => new. Normal. But the problem here, is that the user have to select another time its file.
Any way to avoid that?
Typically you don't want to process files until after validations have run or you're going to repeatedly store files that possibly don't have the associated records. Gems like Paperclip and attachment_fu do this.
If you would rather store the file the first time it's submitted and is valid you can store the file and then do a quick check in your view to see if it's already set for the object you're building a form for, e.g:
<% unless foo.attachment? %>
# file field
<% end %>
Make sense?
You can't. It is security issue.
You can hack it in some browsers, but generally you can't do it
I know the question is old, but now the carrierwave gem has something to retain the file field when the validation fails.
https://github.com/carrierwaveuploader/carrierwave#making-uploads-work-across-form-redisplays
Basically, you will have to add a hidden_field avatar_cache if your model has an uploader mounted on avatar file. You will have to add it to the permit list in the controller (so that Rails wont restrict the field from being submitted to the server)

Rails: Email a pdf generated with prawn to an email sent by ActionMailer?

I have an ecommerce app. I'm using Prawn to generate pdf invoices of orders. I'm using a standard Prawn setup. In views/admin/orders, I have a file called show.pdf.prawn. When the seller is viewing an order in his admin section, he clicks a link that opens the pdf version of the orders/show view. This all works perfectly.
Now, the tricky part. When an order is completed, I send an email to the seller. What I'd like to do is attach the pdf invoice version of orders/show to that email. Is it possible to do this? The documentation on email attachments is pretty limited and I haven't been able to find resources that go through the workflow that I'm describing.
Any guidance is appreciated.
Sending an attachment with an email is fairly easy with ActionMailer:
class InvoiceMailer < ActionMailer::Base
def email_with_attachment(pdf_invoice)
.
.
.
attachment "application/pdf" do |a|
a.filename = "some_invoice.pdf"
a.body = pdf_invoice
end
end
end
One problem you might have with this is generating the pdf file outside of the prawnto method (when using the prawnto plugin)-
If this is is the case I strongly recommend you to use this approach instead.
I had the same problem, i managed to do it by generating the pdf from a model, much easier than evaluating the template. I replied with the answer here :
Save a Prawn PDF as a Paperclip attachment?

How to store private pictures and videos in Ruby on Rails

Here's a story:
User A should be able to upload an image.
User A should be able to set a privacy. ("Public" or "Private").
User B should not be able to access "Private" images of User A.
I'm planning to user Paperclip for dealing with uploads.
If I store the images under "RAILS_ROOT/public/images", anyone who could guess the name of the files might access the files. (e.g., accessing http://example.com/public/images/uploads/john/family.png )
I need to show the images using img tags, so I cannot place a file except public.
How can I ensure that images of a user or group is not accessible by others?
(If I cannot achieve this with Paperclip, what is a good solution?)
You may make your rails server output the contents of image files. This is done via a controller action (most of actions print HTML, but this one will print JPG, for example).
Then you may use your authorization system to restrict access on controller level!
class ImagesController
#Default show Image method streams the file contents.
#File doesn't have to be in public/ dir
def show
send_file #image.filename, :type => #image.content_type,
:disposition => 'inline'
end
# Use your favorite authorization system to restrict access
filter_access_to :show, :require => :view, :attribute_check => :true
end
In HTML code you may use:
<img src="/images/show/5" />
I would have Paperclip use S3 on the back-end, set uploaded files to private, and then use "Query String Request Authentication Alternative" to generate the URLs for my image tags.
http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTAuthentication.html
Here's how I did this in a similar application.
Store your images on Amazon S3 instead of the local file system. Paperclip supports this.
Set your :s3_permissions to "private" in your Paperclip options
In your Image model, define a method that let's you output an authorized, time-limited url for the image.
Mine looks like this:
def s3_url(style = :original, time_limit = 30.minutes)
self.attachment.s3.interface.get_link(attachment.s3_bucket.to_s, attachment.path(style), time_limit)
end
You can then show images to people only if they're authorized to see them (implement that however you like)–and not have to worry about people guessing/viewing private images. It also keeps them from passing URLs around since they expire (the URL has a token in it).
Be warned that it takes time for your app to generate the authorized urls for each image. So, if you have several images on a page, it will affect load time.
If you want to host files yourself, you can perform authentication at the controller level as has been suggested. One of my applications has an AssetController that handles serving of files from the 'private' directory, for example.
One thing I wanted to add is that you should review this guide for setting up X-Sendfile, which will let your application tell the web server to handle actually sending the files. You'll see much better performance with this approach.

What is the best way to show my users a preview of email templates in Ruby on Rails?

My software sends emails for users. I want to show them what the emails will look like before they get sent. However, with ActionMailer conventions, the entire template is in one file. This means the html,head,body tags, etc. Can anyone think of a good way to give my users a preview of what the emails I send out will look like?
Thanks!
I had the same issue. I built out the display with the associated model I was sending rather than in the mailer. I was able to feed sample data or live data to display it to the user.
when it came time to actually send it, I rendered the exact same thing within the mailer view
EDIT:
I apologize for the crap variable names in advance. I am not sure I am allowed to explicitly talk about them :)
Lets say I have a BarMailer function called foo(status,bar)
where status is a test email or a live email and bar is my associated model.
I called deliver_foo("test",bar)
deliver_foo sends out a multipart message so for each part I render_message and pass along variables I need. for example:
p.body = render_message('bar_html', :bar => bar, :other_data => bar.other_data)
so, that render_message is is saying to specifically use the bar_html view (I also have a bar_text for plain text).
this is the contents of my bar_html view:
<%=render :inline => #bar.some_parent.some_other_model.html, :locals => {:other_data => #other_data, :time => Time.now, :bar => #bar }%>
Its a little complicated, but it is based on a template system. By rendering inline everywhere, I am able to use the same code for a number of different functions including previewing and sending. I like this because it becomes a WYSIWIG. No extra code or functionality that could be buggy and muck with the potential output in an email. If it works in one area, it will work in the other. Plus keeping it DRY means I am not going to forget to modify a copy (which I would do frequently, hehe).

Resources