Advertisement
  1. Code
  2. Ruby
  3. Ruby on Rails

Gerando PDFs A Partir de HTML com Rails

Scroll to top
Read Time: 9 min

() translation by (you can also view the original English article)

Há inúmeras formas de gerar PDFs no Ruby e no Rails. Como você já conhece HTML e CSS, então usaremos o PDFKit para gerar arquivos PDFs usando HTML das visões e estilos padrões do Rails.

Introdução ao PDFKit

Internamente, o PDFKit usa o wkhtmltopdf (WebKit HTML to PDF), um mecanismo que pega HTML e CSS, renderiza-os usando Webkit e retorna-os como PDFs de alta qualidade.

Para começar, instalemos o wkhtmltopdf. Podemos baixar o binário ou instalá-lo através do Brew no Mac ou do repositório da sua versão Linux.

Também é preciso instalar a gem pdfkit e executar o seguinte código Ruby para gerar um PDF com o texto "Hello Envato!".

1
require "pdfkit"
2
3
kit = PDFKit.new(<<-HTML)
4
  <p>Hello Envato!</p>

5
HTML

6
7
kit.to_file("hello.pdf")

Criamos um arquivo, chamado hello.pdf, com o texto no topo.

An example of the PDF that was generatedAn example of the PDF that was generatedAn example of the PDF that was generated

PDFKit também gera PDFs a partir de uma URL. Se quisermos gerar um PDF do Google, podemos executar:

1
require "pdfkit"
2
3
PDFKit.new('https://www.google.com', :page_size => 'A3').to_file('google.pdf')

Como vemos, especificamos page_size—por padrão, A4 é usado. Veja a lista completa de opções.

An example of the Google homepage in PDF formatAn example of the Google homepage in PDF formatAn example of the Google homepage in PDF format

Estilizando Seu PDF Usando CSS

Mais cedo, mencionamos que geraremos arquivos PDF usando HTML e CSS. Nesse exemplo, adicionei um pouco de CSS para estilizar o HTML de um fatura simples:

1
require "pdfkit"
2
3
kit = PDFKit.new(<<-HTML)
4
  <style>

5
    * {

6
      color: grey;

7
    }

8
    h1 {

9
      text-align: center;

10
      color: black;

11
      margin-bottom: 100px;

12
    }

13
    .notes {

14
      margin-top: 100px;

15
    }

16


17
    table {

18
      width: 100%;

19
    }

20
    th {

21
      text-align: left;

22
      color: black;

23
      padding-bottom: 15px;

24
    }

25
  </style>

26


27
  <h1>Envato Invoice</h1>

28


29
  <table>

30
    <thead>

31
        <tr>

32
          <th>Description</th>

33
          <th>Price</th>

34
        </tr>

35
      </thead>

36
      <tbody>

37
          <tr>

38
            <td>Monthly Subscription to Tuts+</td>

39
            <td>$15</td>

40
          </tr>

41
      </tbody>

42
  </table>

43


44
  <div class="notes">

45
    <p><strong>Notes:</strong> This invoice was paid on the 23rd of March 2016 using your credit card ending on 1234.</p>

46
  </div>

47
HTML

48
49
kit.to_file("envato_invoice.pdf")

Geraremos o arquivo envato_invoice.pdf se executarmos o script. A foto mostra o resultado do exemplo:

An example of an Envato invoice PDFAn example of an Envato invoice PDFAn example of an Envato invoice PDF

Como vemos, o PDFKit é bem fácil de usar, se já conhecemos HTML e CSS. Podemos customizar ou estilizar o documento o quanto quisermos.

Usando PDFKit A Partir de uma Aplicação Rails

Agora, vejamos como usar o PDFKit em uma aplicação Rails, para gerarmos arquivos PDF dinamicamente, usando dados de nossos modelos. Nessa seção, construiremos uma aplicação Rails simples para gerar a "Fatura Envato", dinamicamente. Criemos uma nova aplicação rails e adicionemos três modelos:

1
$ rails new envato_invoices
2
$ cd envato_invoices
3
4
$ rails generate model invoice date:date client notes
5
$ rails generate model line_item description price:float invoice:references
6
7
$ rake db:migrate

Agora, adicionemos alguns exemplos na base de dados. Adicione o código abaixo no arquivo db/seeds.rb.

1
line_items = LineItem.create([
2
    { description: 'Tuts+ Subscription April 2016', price: 15.0 }, 
3
    { description: 'Ruby eBook', price: 9.90} ])
4
Invoice.create(
5
    client: 'Pedro Alonso', 
6
    total: 24.90, 
7
    line_items: line_items, 
8
    date: Date.new(2016, 4, 1))

Execute rake db:seed em seu terminal para adicionar uma fatura exemplo à base de dados.

Também queremos uma lista de faturas e o detalhamento ddas mesmas, então, usando geradores Rails, execute rails generate controller Invoices index show para criar o controlador e as visões.

app/controllers/invoices_controller.rb

1
class InvoicesController < ApplicationController
2
  def index
3
    @invoices = Invoice.all
4
  end
5
6
  def show
7
    @invoice = Invoice.find(params[:id])
8
  end
9
end

app/views/invoices/index.html.erb

1
<h1>Invoices</h1>
2
<ul>
3
  <% @invoices.each do |invoice| %>
4
  <li>
5
    <%= link_to "#{invoice.id} - #{invoice.client} - #{invoice.date.strftime("%B %d, %Y")} ", invoice_path(invoice) %>
6
  </li>
7
  <% end %>
8
</ul>

Precisamos modificar as rotas do Rails para direcionar para InvoicesController por padrão, então, editemos config/routes.rb:

1
Rails.application.routes.draw do
2
  root to: 'invoices#index'
3
4
  resources :invoices, only: [:index, :show]
5
end

Inicie um servidor com rails server e navegue até localhost:3000 para ver a lista de faturas.

List of invoicesList of invoicesList of invoices

app/views/invoices/show.html.erb

1
<div class="invoice">
2
  <h1>Envato Invoice</h1>
3
4
  <h3>To: <%= @invoice.client %></h3>
5
  <h3>Date: <%= @invoice.date.strftime("%B %d, %Y") %></h3>
6
7
  <table>
8
    <thead>
9
        <tr>
10
          <th>Description</th>
11
          <th>Price</th>
12
        </tr>
13
      </thead>
14
      <tbody>
15
        <% @invoice.line_items.each do |line_item| %>
16
          <tr>
17
            <td><%= line_item.description %></td>
18
            <td><%= number_to_currency(line_item.price) %></td>
19
          </tr>
20
        <% end %>
21
        <tr class="total">
22
          <td style="text-align: right">Total: </td>
23
          <td><%= number_to_currency(@invoice.total) %></span></td>
24
        </tr>
25
      </tbody>
26
  </table>
27
28
  <% if @invoice.notes %>
29
  <div class="notes">
30
    <p><strong>Notes:</strong> <%= @invoice.notes %></p>
31
  </div>
32
  <% end %>
33
</div>

O CSS para a página de detalhes dessa fatura pode ser movido para app/assets/stylesheets/application.scss.

1
.invoice {
2
  width: 700px;
3
  max-width: 700px;
4
  border: 1px solid grey;
5
  margin: 50px;
6
  padding: 50px;
7
8
  h1 {
9
    text-align: center;
10
    margin-bottom: 100px;
11
  }
12
  .notes {
13
    margin-top: 100px;
14
  }
15
16
  table {
17
    width: 90%;
18
    text-align: left;
19
  }
20
  th {
21
    padding-bottom: 15px;
22
  }
23
24
  .total td {
25
    font-size: 20px;
26
    font-weight: bold;
27
    padding-top: 25px;
28
  }
29
}

Então, ao clicarmos em uma fatura na listagem principal, veremos seu detalhamento:

Invoice viewInvoice viewInvoice view

Agora, estamos prontos para adicionar a funcionalidade de visualizar ou baixar faturas como PDF, à nossa aplicação Rails.

Classe InvoicePdf para Lidar com Renderização de PDF

Para renderizar as faturas como PDF, precisamos adicionar 3 gems ao Gemfile: PDFKit, render_anywhere e wkhtmltopdf-binary. Por padrão, Rails renderiza visões apenas em controladores, mas usando render_anywhere, podemos renderizá-las em modelos do domínio ou em serviços em segundo plano.

1
gem 'pdfkit'
2
gem 'render_anywhere'
3
gem 'wkhtmltopdf-binary'

Para não poluir nosso controlador com muita lógica, criaremos uma nova classe InvoicePdf dentro de app/models para envolver a lógica de geração do PDF.

1
require "render_anywhere"
2
3
class InvoicePdf
4
  include RenderAnywhere
5
6
  def initialize(invoice)
7
    @invoice = invoice
8
  end
9
10
  def to_pdf
11
    kit = PDFKit.new(as_html, page_size: 'A4')
12
    kit.to_file("#{Rails.root}/public/invoice.pdf")
13
  end
14
15
  def filename
16
    "Invoice #{invoice.id}.pdf"
17
  end
18
19
  private
20
21
    attr_reader :invoice
22
23
    def as_html
24
      render template: "invoices/pdf", layout: "invoice_pdf", locals: { invoice: invoice }
25
    end
26
end

Essa classe receberá a fatura a ser renderizada por parâmetro, em seu construtor. O método privado as_html lê as visões invoices/pdf e layout_pdf, que serão usadas para gerar o HTML necessário para a renderização do PDF. Por último, o método to_pdf usa o PDFKit para salvar o PDF no diretório public do Rails.

Provavelmente, você gerará um nome dinâmico para o PDF para que não seja sobrescrito por acidente em sua aplicação real. Talvez até queira salvar o arquivo na AWS S3 ou em um diretório privado, mas essas ações estão além desse tutorial.

/app/views/invoices/pdf.html.erb

1
<div class="invoice">
2
  <h1>Envato Invoice</h1>

3


4
  <h3>To: <%= invoice.client %></h3>
5
  <h3>Date: <%= invoice.date.strftime("%B %d, %Y") %></h3>

6


7
  <table>

8
    <thead>

9
        <tr>

10
          <th>Description</th>

11
          <th>Price</th>

12
        </tr>

13
      </thead>

14
      <tbody>

15
        <% invoice.line_items.each do |line_item| %>

16
          <tr>

17
            <td><%= line_item.description %></td>
18
            <td><%= number_to_currency(line_item.price) %></td>

19
          </tr>

20
        <% end %>

21
        <tr class="total">
22
          <td style="text-align: right">Total: </td>

23
          <td><%= number_to_currency(invoice.total) %></span></td>

24
        </tr>
25
      </tbody>

26
  </table>
27
28
  <% if invoice.notes %>

29
  <div class="notes">
30
    <p><strong>Notes:</strong> <%= invoice.notes %></p>

31
  </div>

32
  <% end %>

33
</div>

/app/views/layouts/invoice_pdf.erb

1
<!DOCTYPE html>
2
<html>
3
<head>
4
  <title>Envato Invoices</title>

5
  <style>

6
    <%= Rails.application.assets.find_asset('application.scss').to_s %>

7
  </style>
8
</head>

9
<body>

10
  <%= yield %>

11
</body>
12
</html>

Algo a se notar nesse arquivo de layout é que renderizamos os estilos dentro dele. WkHtmlToPdf funciona melhor se fizermos desse jeito.

DownloadsController para Renderizar o PDF da Fatura

Agora, precisamos de uma rota e um controlador que chame a classe InvoicePdf para enviar o arquivo PDF para o navegador. Então, edite o arquivo config/routes.rb, assim:

1
Rails.application.routes.draw do
2
  root to: "invoices#index"
3
4
  resources :invoices, only: [:index, :show] do
5
    resource :download, only: [:show]
6
  end
7
end

Se usarmos rake routes, veremos a lista de rotas disponíveis na aplicação:

1
          Prefix Verb URI Pattern                              Controller#Action
2
            root GET  /                                        invoices#index
3
invoice_download GET  /invoices/:invoice_id/download(.:format) downloads#show
4
        invoices GET  /invoices(.:format)                      invoices#index
5
         invoice GET  /invoices/:id(.:format)                  invoices#show

Crie app/controllers/downloads_controller.rb:

1
class DownloadsController < ApplicationController
2
3
  def show
4
    respond_to do |format|
5
      format.pdf { send_invoice_pdf }
6
    end
7
  end
8
9
  private
10
11
  def invoice_pdf
12
    invoice = Invoice.find(params[:invoice_id])
13
    InvoicePdf.new(invoice)
14
  end
15
16
  def send_invoice_pdf
17
    send_file invoice_pdf.to_pdf,
18
      filename: invoice_pdf.filename,
19
      type: "application/pdf",
20
      disposition: "inline"
21
  end
22
end

Como podemos ver, quando a requisição buscar um arquivo PDF, o send_invoice_pdf a processará. O método invoice_pdf encontrará a fatura usando o ID e criará uma instância de InvoicePdf. Então, send_invoice_pdf chamará o método to_pdf, enviando o arquivo gerado para o navegador.

Outra coisa a se notar é que estamos passando disposition: "inline" para send_file. Esse parâmetro envia o arquivo para o navegador, que o mostrará na tela. Se quiser forçar o download do arquivo, você precisa passar disposition: "attachment".

Adicione um botão de download para a fatura na visão app/views/invoices/show.html.erb:

1
  <%= link_to "Download PDF",

2
    invoice_download_path(@invoice, format: "pdf"),

3
    target: "_blank",

4
    class: "download" %>

Execute a aplicação, navegue pelos detalhes da fatura, clique em download e veja uma nova abar mostrando a fatura em PDF.

Invoice view PDF dynamically generatedInvoice view PDF dynamically generatedInvoice view PDF dynamically generated

Renderizando PDF como HTML Durante Desenvolvimento

Ao criar o código HTML para seu PDF, é preciso gerar um novo PDF toda vez que quiser testar uma mudança. Algumas vezes, isso pode ser lento. Por isso, pode ser bem útil poder visualizar o HTML gerado que será convertido em PDF. Apenas precisamos editar o arquivo /app/controllers/downloads_controller.rb:

1
class DownloadsController < ApplicationController
2
3
  def show
4
    respond_to do |format|
5
      format.pdf { send_invoice_pdf }
6
7
      if Rails.env.development?
8
        format.html { render_sample_html }
9
      end
10
    end
11
  end
12
13
  private
14
15
  def invoice
16
    Invoice.find(params[:invoice_id])
17
  end
18
19
  def invoice_pdf
20
    InvoicePdf.new(invoice)
21
  end
22
23
  def send_invoice_pdf
24
    send_file invoice_pdf.to_pdf,
25
      filename: invoice_pdf.filename,
26
      type: "application/pdf",
27
      disposition: "inline"
28
  end
29
30
  def render_sample_html
31
    render template: "invoices/pdf", layout: "invoice_pdf", locals: { invoice: invoice }
32
  end
33
end

Agora, o método show também lida com requisições HTML em modo de desenvolvimento. A rota para a fatura em PDF seria algo assim: http://localhost:3000/invoices/1/download.pdf. Se altararmos para http://localhost:3000/invoices/1/download.html, podemos ver o HTML usado para gerar o PDF da fatura.

Dado o código acima, gerar arquivos PDF usando Ruby on Rails é bem simples e direto, assumindo que seja familiar com a lingugem Ruby e a framework Rails. Talvez, o melhor aspecto desse processo é que não é preciso aprender uma nova linguagem ou coisas específicas sobre geração de PDF.

Espero que esse tutorial tenha sido útli. Por favor, faça quaisquer perguntas, comentários ou feedback na seção de comentário e ficarei feliz em responder!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.