Rails 3 -Render PDF from view and attach to email - ruby-on-rails

I have been using Wicked_pdf to render a view as a PDF and actionmailer to send emails, but I can't get them to work together. I want to attach a PDF version of a certain view to an email using actionmailer and send it out by clicking a link or a button. I have a link_to command that sends out an email. Here is my controller that gets the email generated:
def sendemail
#user = User.find(params[:id])
Sendpdf.send_report(#user).deliver
redirect_to user_path(#user)
flash[:notice] = 'Email has been sent!'
end
Here is what I have in my actionmailer:
class Sendpdf < ActionMailer::Base
default :from => "myemail#email.com"
def send_report(user)
#user = user
attachment "application/pdf" do |a|
a.body = #Something should go here, maybe WickedPDF.new.something?
a.filename = 'MyPDF'
end
mail(:to => user.email, :subject => "awesome pdf, check it")
end
end
I have seen many questions and answers, most dealing with Prawn. It seems like there should be a simple answer to this. Can anyone help?
UPDATE I'm grateful for a suggestion to use as an alternative option in the answer below. However, I would really like to learn how to render a view as a PDF and attach it to my email. I am open to using something different like Prawn or anything else if I need to.

2 good ways to do this the way you want:
1: Create the pdf in the controller, then send that to the email as a param.
# controller
def sendemail
#user = User.find(params[:id])
pdf = render_to_string :pdf => 'MyPDF'
Sendpdf.send_report(#user, pdf).deliver
redirect_to user_path(#user)
flash[:notice] = 'Email has been sent!'
end
# mailer
def send_report(user, pdf)
#user = user
attachments['MyPDF.pdf'] = pdf
mail(:to => user.email, :subject => "awesome pdf, check it")
end
2: Create the pdf in the mailer directly (a little more involved, but can be called from a model)
def send_report(user)
#user = user
mail(:to => user.email, :subject => "awesome pdf, check it") do |format|
format.text # renders send_report.text.erb for body of email
format.pdf do
attachments['MyPDF.pdf'] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => 'MyPDF',:template => 'reports/show.pdf.erb')
)
end
end
end

There are 2 ways for it.
Either, you want the pdf to be embedded in the email you are sending, so that when the user downloads the pdf from the email, there is no request to the render new pdf action for your respective controller.
I don't know how to do this efficiently because I have never done this before.
Or, you just provide a link to the pdf in your email and when the user clicks on it, now the action for creating the pdf is called, and then the user can download it.
This way, if there is a lot of burden on the server for the downloading of the pdf's, you can redirect these requests somewhere else. In short, there is a huge scope for efficiency.
A sample code for the 2nd method(code provided was written by using PDFkit, so change accordingly):
class PdfsController < ApplicationController
def pdf
respond_to do |format|
format.pdf { render :text => wickedPDF.new( Pdf.find(params[:id]).content ).to_pdf }
end
end
...
end
Replace the Pdf.find(params[:id]).content as per your choice, and the to_pdf method, as per wickedPDF.
Then, you can simply pass the link for the pdf download in your email like this
<%= link_to "Download", pdf_pdf_path(pdf, :format => "pdf") %>
or whatever suits as per wickedPDF.

Related

Rails 4 Wick_pdf sends attaches blank pdf

I'm using wick_pdf in my Rails 4 app to convert several html templates into pdfs. I then attach these pdfs to an outbound email. The email and the pdfs are sending correctly, but there is an additional blank pdf (occasionally not completely) that gets sent along with my desired pdfs. I've attached my code below. Please let me know if you have any suggestions.
def approval_notification(id)
#user = User.find(id)
esign = WickedPdf.new.pdf_from_string(render_to_string pdf: "esign", template: "disclosures/pdfs/esign_pdf.html.erb", formats: :html, encoding: "UTF-8")
mail(:to => #user.email, :subject => 'Congratulations! You have been approved!') do |format|
format.html
format.pdf do
attachments['Electronic_Signature.pdf'] = esign
end
end
end
Update:
Alright, I managed to fix the issue. I still don't completely understand what was wrong so please explain if you know. The revised code is below:
def approval_notification(id)
#user = User.find(id)
esign = WickedPdf.new.pdf_from_string(render_to_string pdf: "esign", template: "disclosures/pdfs/esign_pdf.html.erb", formats: :html, encoding: "UTF-8")
attachments['Electronic_Signature.pdf'] = esign
mail(:to => #user.email, :subject => 'Congratulations! You have been approved!')
end

S3 upload in fewest possible steps

So I setup my app to upload documents to S3 (following Ryan Bates' railscast). Currently, there's a form to upload a file is on the index page for the document resource. On successful upload to S3, that form renders the new actions where the user can rename the file (as it's named locally).
But I'd like to refactor this to eliminate redirecting the user to the '/new' page. I don't want to offer the user a chance to rename the file.
There's got to be a way to upload the file to S3 and then have it post to the create action.
This is what my controller looks like now.
class DocumentsController < ApplicationController
before_filter :authenticate_user!
def index
#page_title = "Docs"
#documents = household.documents.all
#uploader = Document.new.file_upload
#uploader.success_action_redirect = new_document_url
end
def new
#page_title = "New Doc"
#document = Document.new(household_id: household.id, key: params[:key])
end
def create
#doc = Document.new(params[:document])
if #doc.save
flash[:notice] = "Consider it done!"
redirect_to documents_path
else
flash[:notice] = "Oops. Please try again."
render 'new'
end
end
end
How do I simplify this situation and avoid rendering the /new page? Instead of rendering the /new, I want to just post to the create action. I need modify this line:
#uploader.success_action_redirect = new_document_url
but I don't know what it should be.
Perhaps you can create a method in DocumentsController that serves as a REST endpoint. You post from the client via Ajax and then upload to S3 in the endpoint method. This way the client's experience remains unchanged with the upload.
Something like this:
def upload
uploader = Document.new(params_from_ajax_post).file_upload
respond_to do |format|
format.html { render :json => {:message => 'Upload successful.'} }
end
end
In routes.rb:
match 'documents/upload' => 'documents#upload', :via => :post, :as => 'upload'

Rails - Mailer Instance Variable Nil in Email View

I am attempting to create beta invitations using the structure from railscasts episode 124, updated for rails 3.2.8.
Currently, the invitation email gets sent, but does not contain the url (which includes the invitation token) for users to follow to sign up because the instance variable I am creating in ActionMailer (#invitation_link) is nil in the view. Inspecting #invitation_link in the ActionMailer controller shows that it is pointing to the correct url, but it is nil in the view.
I have also checked out the following questions and none of the solutions have worked for me:
How do you use an instance variable with mailer in Ruby on Rails?
https://stackoverflow.com/questions/5831038/unable-to-access-instance-variable-in-mailer-view
Actionmailer instance variable problem Ruby on Rails
ActionMailer pass local variables to the erb template
Relevant code snippets below:
invitations_controller.rb
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if signed_in?
InvitationMailer.invitation(#invitation).deliver
flash[:notice] = "Thank you, invitation sent."
redirect_to current_user
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_url
end
else
render :action => 'new'
end
end
end
in invitation_mailer.rb file
class InvitationMailer < ActionMailer::Base
default from: "holler#thesite.com", content_type: "text/html"
def invitation(invitation)
mail to: invitation.recipient_email, subject: "Invitation"
#invitation_link = invited_url(invitation.token)
invitation.update_attribute(:sent_at, Time.now)
end
end
views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= #invitation_link %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
routes.rb (only showing relevant line)
match '/invited/:invitation_token', to: 'users#new_invitee', as: 'invited'
try this way
This is your InvitationMailer
def invitation(invitation)
#invitation = invitation
mail(:to => #invitation.recipient_email, :subject => "Invitation")
end
now, in your InvitationsController
if signed_in?
#invitation.update_attribute(:sent_at, Time.now)
InvitationMailer.invitation(#invitation).deliver
...
else
...
end
now, views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= invited_url(#invitation.token) %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
try this...
#invitation_link = invited_url(invitation.token, :host => "localhost:3000")

Rails and multipart emails with user-defined part selection and html to text autoconversion

Our Rails (3.0.14) application has user profiles where users can choose whether they want to receive HTML formatted emails. To keep things DRY, I would like to only setup one set of templates for all mails (HTML) and then use my own String extension dehtml (basically strip_tags with some formatting modifications) on the text/plain part. Also, I would like to keep the mailer code DRY.
So far, our mailer methods look like this:
def signup_confirmation(user)
#user = user
mail(:to => #user.email, :subject => ..., ...)
end
1. DRY templates: If possible, I would like to avoid having to create 200 additional mail templates, and autocreate the text/plain part from the HTML template. This is the basic idea (dehtml is my own String extension):
def signup_confirmation(user)
#user = user
mail(:to => #user.email, :subject => ..., ...) do |format|
format.html
format.text { render(:file => 'signup_notification.html').dehtml }
end
end
However, this fails with a 'missing template' error. How do I tell Rails to use the HTML template in both cases? I tried appending :formats => :html and :handler => :html but this didn't help.
I don't have a solution here right now. Any ideas?
2: DRY mailer methods:
Since our users should be able to decide whether they want to have HTML or not, the above method will look something like
def signup_confirmation(user)
#user = user
attachments.inline["email-header.jpg"] = File.read(...) if #user.wants_html
mail(:to => #user.email, :subject => ..., ...) do |format|
format.html if #user.wants_html
format.text { render(:file => 'signup_notification.html').dehtml }
end
end
Altogether, this triples the LOC in each method. I would like to DRY this up (since it will have to be inserted into at least 200 mailer methods) as far as possible. One idea would be to write my own mail method (let's call it mymail) as something like
def mymail(user, p={})
attachments.inline["email-header.jpg"] = File.read(...) if user.wants_html
mail(p) do |format|
format.html if user.wants_html
format.text
end
end
(ignoring the above text template problem for now) and then change each call to mail to mymail, as in
def signup_confirmation(user)
#user = user
attachments.inline["email-header.jpg"] = File.read(...) if #user.wants_html
mymail(user, { :to => #user.email, :subject => ..., ... })
end
This works. But is it good practice? Where do I best put mymail - in a helper?
Any insights and recommendations welcome!
I did something very similar to your first solution a long time ago. I don't really remember why it hat to be the way it is, but this is working for me:
def my_mail
mail(:to => #user.email ...) do |format|
format.text { convert_html_to_plain(__method__) } # first text
format.html # then html
end
end
def convert_html_to_plain(method)
old_formats = self.formats
self.formats = ["html"]
rendered = render "#{method}", :layout => false
self.formats = old_formats
# strip tags, reformat, etc. from rendered
rendered << render(:partial => "plaintext_footer", :locals => {:user => #user}, :formats => [:text] )
end

validates_confirmation_of :message doesn't appear

subsriber.rb
class Subscriber < ActiveRecord::Base
validates_confirmation_of :email, :message => "Your emails don't match!"
end
I have this in my rails app. When I create a new record without matching email
Here's my create action:
def create
#subscriber = Subscriber.new(params[:subscriber])
if #subscriber.save
redirect_to root_path, :notice => "You've been subscribed!"
else
render 'new'
end
end
How do I make the error message show up in the view file? I don't see anything in the docs saying I need to add something to my views but the message is not showing up.
You actually do need to add something to your view to show the message.
Rails normally does this for you when you create a scaffold, but if you need to do it manually for a field, you need to add something like this to your HTML template:
<%= f.error_messages_for :email_confirmation %>

Resources