Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render a .pdf.prawn view into a file #32

Open
renandhein opened this issue Jun 29, 2018 · 11 comments
Open

Render a .pdf.prawn view into a file #32

renandhein opened this issue Jun 29, 2018 · 11 comments

Comments

@renandhein
Copy link

Hey!

Big fan of this project. It works flawlessly and allows us to put the code that composes a PDF in the views folder which is where I've always believed something like that belongs.

I have a pdf working as a view exactly like the main page's example with prawn_document and everything. The controller responds to the .pdf format and renders it, all is good and well.

However, I would like to send the generated PDF as an e-mail attachment and to do that I need to have the PDF rendered as if it was a file in my controller. Is there any way to do render a .pdf.prawn view into a file like that?

Let's say I wanted to render a pdf view into a file and save it in the disk, for example. Is that possible in the current state of the gem? I know that using raw prawn that's not hard to do, but with the PDF code inside a view I can't seem to find a way. I couldn't find anything on that topic anywhere.

Thank you in advance!

@westonganger
Copy link
Collaborator

@gaffneyc can you comment on this? If so maybe you could also add a PR to the README.md that describes thisthis functionality? That'd be sweet.

@gaffneyc
Copy link
Contributor

@westonganger Yeah, of course.

@renandhein That's exactly what I implemented a couple days ago on the cron job monitoring service I build (Dead Man's Snitch). I'll put the details here but I'll also open a PR (when I have a chance) to update the readme.

We have a route (get "/invoices/:id.pdf" => "invoices#show") for downloading invoices. You can use render_to_string("path/to/view", formats: [:pdf]) in your Mailer to render the PDF. For example, here is our Mailer code:

class BillingMailer < ApplicationMailer
  def send_invoice(invoice)
    @invoice = invoice

    filename = "dms-receipt-#{@invoice.created_at.to_date}.pdf"
    attachments[filename] = {
      mime_type: "application/pdf",
      content: render_to_string("invoices/show", formats: [:pdf]),
    }

    mail({ to: @invoice.emails, subject: "Monthly subscription paid" })
  end
end

@westonganger
Copy link
Collaborator

westonganger commented Jun 30, 2018

@gaffneyc that is one way to do it. You can also manually save a PDF, I have done the following before:

### Load template to String
template = File.read('path/to/file.pdf.prawn')

### Initialize PrawnRails::Document, using normal prawn would be Prawn::Document
pdf_doc = PrawnRails::Document.new(page_size: 'A4', page_layout: :landscape, top_margin: 100)

pdf_doc.instance_eval do
  ### set any instances variables
  @items = Item.where(user_id: user_id)

  ### evaluate the template with your instance variables set above
  eval(template) 
end

### Render to file
pdf_doc.render_file('path/to/file.pdf')
# or
File.open('path/to/file.pdf', 'wb') do |f|
  f.write pdf_doc.render
end

Note: The template file must NOT contain prawn_document do .. end block. If you are using it in a Rails action as well and require this then you may need to do some massaging to exclude the prawn_document block. A ghetto example:

### Get template except first and last lines (which contain `prawn_document` block)
template = File.readlines('path/to/file.pdf.prawn')[1..-2].join 

Relying on first and last line is probably pretty fragile, your likely better off with a regex or something. Would love to hear a better recommendation.

@gaffneyc
Copy link
Contributor

Sorry for the confusion, having a route isn't required. I mentioned it to show we were able to reuse the same logic both in the mailer and in our controller. render_to_string takes a path to a template that can be anywhere in app/views. For example, render_to_string("billing_mailer/invoice", formats: [:pdf]) would work if you only needed to render it for the mailer.

I prefer this approach as it allows us to leverage Rails's rendering pipeline and we're able to keep the views consistent across all of the places we may need to render the PDF. Having caveats like the view should not contain prawn_document only add to the complexity of using the view. I also avoid using eval as much as possible (instance_exec is often the safer option).

@renandhein
Copy link
Author

@gaffneyc @westonganger Thank you so much to both of you for the help and inputs! I think both solutions are valid, though I did end up preferring the render_to_string solution as indeed it allows me to keep the view intact for both general rendering and mailer purposes.

I do have to point out though that using Rails 5.2 I wasn't able to use render_to_string inside a mailer due to render_to_string's internals calling methods that expect a controller environment (as far as I could tell). I had to fetch and build up the file contents in my controller before calling the mailer.

Still, render_to_string works beautifully and gets the job done. Creating a file as @westonganger suggested is a nice alternative, though.

Thank you so much!

@gaffneyc
Copy link
Contributor

gaffneyc commented Jul 2, 2018

@renandhein Out of curiosity, did you get undefined method response for [MailerClass] or something similar?

@renandhein
Copy link
Author

@gaffneyc Exactly! I did wonder if you ran into that while working on Dead Man's Snitch?

Awesome project by the way! I bookmarked it :)

@gaffneyc
Copy link
Contributor

gaffneyc commented Jul 2, 2018

@renandhein I did actually, make sure you're on prawn-rails >= "1.2.0" as I recently fixed that in #30.

And thanks! I hope you find a use for it at some point :)

@renandhein
Copy link
Author

@gaffneyc Thank you! Yeah, sorry about that, I am actually still at 1.0. I'll look into updating it and then things should work as you suggested.

Again, many thanks for all your support!

@candland
Copy link

candland commented Dec 10, 2020

Another option for Rails 6, maybe 5.

ApplicationController.render(
  template: "shared/example",
  formats: [:pdf],
  assigns: { user: @user }
)

@westonganger
Copy link
Collaborator

westonganger commented Sep 16, 2022

Please ensure you are passing the :formats option as an array. Example:

ApplicationController.render(template: "shared/example", formats: [:pdf], assigns: { user: user })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants