
Introdução 🙂
Criar PDFs para exportar dados, gerar boletos e etc é uma tarefa comum em vários sistemas e é claro que o Ruby On Rails possui várias maneiras de resolver este problema. A minha preferida é utilizando a gem Prawn porque ela te permite realizar customizações complexas de uma maneira bem simples e intuitiva.
O que vamos aprender?
Nesse tutorial eu vou abordar a criação de dois tipos de documentos em PDF, o primeiro é um contrato gerado através de alguns dados básicos como nome do cliente, descrição, valor e etc e o segundo documento vai ser um PDF com dois gráficos diferentes baseados nos dados de despesas de uma empresa.
Nós vamos aprender como usar a sintaxe básica do prawn e também como gerar gráficos incríveis usando o gruff.
INGREDIENTES
- Ruby 2.3.1
- Rails 5
- Sqlite3
- Prawn(Gem)
- Gruff(Gem)
Objetivos
Criar um projeto que permita exportar os dados de duas tabelas de uma empresa em PDF.
A primeira gerando um contrato (em PDF) e a segunda um gráfico sobre as contas de uma empresa (em PDF).
Passo a Passo
- Criar a estrutura do Projeto
- Incluir os links e métodos de export
- Criar nossos métodos para gerar os PDFs
Mãos à Obra \o/
Parte 1 – Criando o Projeto
Primeiro vamos criar nosso projeto Rails, nossas tabelas e também dois scaffolds básicos para podermos gerenciar os dados que serão usados para gerar os PDFs.
1 – Para começar, rode no seu console o comando para gerar o projeto:
1 |
rails new generate_pdf |
2 – Inclua no seu Gemfile o prawn e o gruff:
1 2 3 4 |
# Gem para gerar os pdfs gem 'prawn-rails' # Gem para gerar os gráficos gem 'gruff' |
3 – O gruff tem como dependências os seguintes softwares, para instalar no ubuntu rode:
1 |
sudo apt-get install imagemagick libmagickcore-dev libmagickwand-dev |
*Caso você esteja em outro sistema, acesse este link para instalar.
4 – Vamos instalar nossas Gems, rode:
1 |
bundle install |
5 – Vamos criar nossos scaffolds para poder gerenciar os dados das tabelas que usaremos pra gerar os PDFs, rode este comando para o primeiro scaffold:
1 |
rails g scaffold agreement client_name:string description:text price:decimal |
E agora este para o segundo, rode:
1 |
rails g scaffold spending value:decimal section:integer |
6 – Vamos rodar nossas migrations (como estamos usando o sqlite3 não precisamos rodar o rake db:create antes)
1 |
rake db:migrate |
7 – Vamos rodar o servidor e criar um novo agreement, siga os passos:
- Rode o servidor
1 |
rails s |
- Agora para ver uma tela semelhante a que está a baixo vá até http://localhost:3000/agreements:
- Clique em “New Agreement” e preencha o formulário.
- Agora acessando http://localhost:3000/agreements seu resultado deve ser semelhante ao da imagem a baixo:
Pronto \o/, nosso projeto foi criado, agora vamos preparar ele para exportar nossos dados.
Parte 2 – Preparando os Exports
Nesta fase nós vamos incluir no nosso projeto os botões de export e também vamos preparar nossos controllers para chamarem nosso módulo gerador de PDF quando ele estiver pronto.
1 – Vamos acrescentar as rotas para exportarmos nossas tabelas. Vá até routes.rb e substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 |
Rails.application.routes.draw do resources :spendings # /spendings_export get '/spending_export' => 'spendings#export' resources :agreements do member do # /agreements/:id/export get 'export' end end end |
2 – Vá até o arquivo “app/views/agreements/index.html.erb”, e substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<p id="notice"><%= notice %></p> <h1>Agreements</h1> <% @agreements.each do |agreement| %> <% end %> <table> <thead> <tr> <th>Client name</th> <th>Description</th> <th>Price</th> <th colspan="3"></th> </tr> </thead> <tbody> <tr> <td><%= agreement.client_name %></td> <td><%= agreement.description %></td> <td><%= agreement.price %></td> <td><%= link_to 'Show', agreement %></td> <td><%= link_to 'Edit', edit_agreement_path(agreement) %></td> <td><%= link_to 'Destroy', agreement, method: :delete, data: { confirm: 'Are you sure?' } %></td> <td><%= link_to 'Export', export_agreement_path(agreement), target: "_blank" %></td> </tr> </tbody> </table> <%= link_to 'New Agreement', new_agreement_path %> |
*Note que incluímos o link para dar o export de cada agreement.
3 – Agora vá até o arquivo “app/views/spendings/index.html.erb”, e substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<p id="notice"><%= notice %></p> <h1>Spendings</h1> <% @spendings.each do |spending| %> <% end %> <table> <thead> <tr> <th>Value</th> <th>Section</th> <th colspan="3"></th> </tr> </thead> <tbody> <tr> <td><%= spending.value %></td> <td><%= spending.section %></td> <td><%= link_to 'Show', spending %></td> <td><%= link_to 'Edit', edit_spending_path(spending) %></td> <td><%= link_to 'Destroy', spending, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> </tbody> </table> <%= link_to 'New Spending', new_spending_path %> <%= link_to 'Export To Graph', spending_export_path, target: "_blank"%> <strong style="font-size: 14px;">*Note que incluímos o link para dar o export das spendings.</strong> |
4 – Agora vá até o controller “app/controllers/agreement_controller.rb” e substitua o conteúdo dele por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
class AgreementsController < ApplicationController # No Before Action nós adicionamos também o export para que ele consiga pegar o agreement correto before_action :set_agreement, only: [:show, :edit, :update, :destroy, :export] # Nós incluimos aqui a lib que vamos criar chamada generate_pdf.rb require './lib/generate_pdf' # GET /agreements # GET /agreements.json def index @agreements = Agreement.all end # GET /agreements/1 # GET /agreements/1.json def show end # GET /agreements/new def new @agreement = Agreement.new end # GET /agreements/1/edit def edit end # POST /agreements # POST /agreements.json def create @agreement = Agreement.new(agreement_params) respond_to do |format| if @agreement.save format.html { redirect_to @agreement, notice: 'Agreement was successfully created.' } format.json { render :show, status: :created, location: @agreement } else format.html { render :new } format.json { render json: @agreement.errors, status: :unprocessable_entity } end end end # PATCH/PUT /agreements/1 # PATCH/PUT /agreements/1.json def update respond_to do |format| if @agreement.update(agreement_params) format.html { redirect_to @agreement, notice: 'Agreement was successfully updated.' } format.json { render :show, status: :ok, location: @agreement } else format.html { render :edit } format.json { render json: @agreement.errors, status: :unprocessable_entity } end end end # DELETE /agreements/1 # DELETE /agreements/1.json def destroy @agreement.destroy respond_to do |format| format.html { redirect_to agreements_url, notice: 'Agreement was successfully destroyed.' } format.json { head :no_content } end end # Criamos este método que vai chamar nossa lib para gerar o PDF e depois redirecionar o user para o arquivo PDF def export GeneratePdf::agreement(@agreement.client_name, @agreement.description, @agreement.price) redirect_to '/agreement.pdf' end private def set_agreement @agreement = Agreement.find(params['id']) end def agreement_params params.require(:agreement).permit(:client_name, :description, :price) end end |
* Se você chamar o controller no browser vai quebrar porque ainda não criamos nossa módulo de export, vamos fazer isso na próxima parte.
* Note que o que incluímos foi o método export, o carregamento da lib que vamos criar e o método export no set_agreement para carregar corretamente o record. (Veja com calma os comentários no código)
5 – Agora vá até o controller “app/controllers/spending_controller.rb” e substitua o conteúdo dele por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
class SpendingsController < ApplicationController before_action :set_spending, only: [:show, :edit, :update, :destroy] # Incluimos a Lib que vamos criar para podermos chama-la no nosso método require './lib/generate_pdf' # GET /spendings # GET /spendings.json def index @spendings = Spending.all end # GET /spendings/1 # GET /spendings/1.json def show end # GET /spendings/new def new @spending = Spending.new end # GET /spendings/1/edit def edit end # POST /spendings # POST /spendings.json def create @spending = Spending.new(spending_params) respond_to do |format| if @spending.save format.html { redirect_to @spending, notice: 'Spending was successfully created.' } format.json { render :show, status: :created, location: @spending } else format.html { render :new } format.json { render json: @spending.errors, status: :unprocessable_entity } end end end # PATCH/PUT /spendings/1 # PATCH/PUT /spendings/1.json def update respond_to do |format| if @spending.update(spending_params) format.html { redirect_to @spending, notice: 'Spending was successfully updated.' } format.json { render :show, status: :ok, location: @spending } else format.html { render :edit } format.json { render json: @spending.errors, status: :unprocessable_entity } end end end # DELETE /spendings/1 # DELETE /spendings/1.json def destroy @spending.destroy respond_to do |format| format.html { redirect_to spendings_url, notice: 'Spending was successfully destroyed.' } format.json { head :no_content } end end # Criamos o método export para chamar a lib que gera o PDF e depois redirecionar o usuário para baixo o PDF def export GeneratePdf::spending(Spending.all.map {|s| [s.section, s.value.to_f]}) redirect_to '/spending.pdf' end private # Use callbacks to share common setup or constraints between actions. def set_spending @spending = Spending.find(params['id']) end # Never trust parameters from the scary internet, only allow the white list through. def spending_params params.require(:spending).permit(:value, :section) end end |
* Se você chamar o controller no browser vai quebrar porque ainda não criamos nossa módulo de export, vamos fazer isso na próxima parte.
* Note que o que incluímos foi o método export e o carregamento da lib que vamos criar. (Veja com calma os comentários no código)
Pronto Agora vamos para a parte divertida \o/
Parte 3 – Criando os métodos de Export
Nesta parte nós vamos criar dois métodos dentro de um módulo para gerar nossos PDFs, para fazer isso vamos criar um arquivo dentro do diretório lib e incluir nossas dependências (prawn para o PDF e gruff para gerar as imagens dos gráficos).
Eu comentei linha a linha do código para você entender o funcionamento do Prawn e Guff então, acompanhe com calma o código 🙂
- Crie um arquivo chamado “generate_pdf.rb” no diretório “lib” da sua aplicação.
1touch lib/generate_pdf.rb - Agora copie dentro desse arquivo o seguinte código:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119require 'prawn'require 'gruff'module GeneratePdfPDF_OPTIONS = {# Escolhe o Page size como uma folha A4:page_size => "A4",# Define o formato do layout como portrait (poderia ser landscape):page_layout => :portrait,# Define a margem do documento:margin => [40, 75]}def self.agreement name, details, price# Apenas uma string aleatório para termos um corpo de texto pro contratolorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec elementum nulla id dignissim iaculis. Vestibulum a egestas elit, vitae feugiat velit. Vestibulum consectetur non neque sit amet tristique. Maecenas sollicitudin enim elit, in auctor ligula facilisis sit amet. Fusce imperdiet risus sed bibendum hendrerit. Sed vitae ante sit amet sapien aliquam consequat. Duis sed magna dignissim, lobortis tortor nec, suscipit velit. Nulla sit amet fringilla nisl. Integer tempor mauris vitae augue lobortis posuere. Ut quis tellus purus. Nullam dolor mauris, egestas varius ligula non, cursus faucibus orci sectetur non neque sit amet tristique. Maecenas sollicitudin enim elit, in auctor ligula facilisis sit amet. Fusce imperdiet risus sed bibendum hendrerit. Sed vitae ante sit amet sapien aliquam consequat."Prawn::Document.new(PDF_OPTIONS) do |pdf|# Define a cor do traçadopdf.fill_color "666666"# Cria um texto com tamanho 30 PDF Points, bold alinha no centropdf.text "Agreement", :size => 32, :style => :bold, :align => :center# Move 80 PDF points para baixo o cursorpdf.move_down 80# Escreve o texto do contrato com o tamanho de 14 PDF points, com o alinhamento justifypdf.text "#{lorem_ipsum}", :size => 12, :align => :justify, :inline_format => true# Move mais 30 PDF points para baixo o cursorpdf.move_down 30# Escreve o texto com os detalhes que o usuário entroupdf.text "#{details}", :size => 12, :align => :justify, :inline_format => true# Move mais 30 PDF points para baixo o cursorpdf.move_down 10# Adiciona o nome com 12 PDF points, justify e com o formato inline (Observe que o <b></b> funciona para deixar em negrito)pdf.text "Com o cliente: <b>#{name}</b> por R$#{price}", :size => 12, :align => :justify, :inline_format => true# Muda de font para Helveticapdf.font "Helvetica"# Inclui um texto com um link clicável (usando a tag link) no bottom da folha do lado esquerdo e coloca uma cor especifica nessa parte (usando a tag color)pdf.text "Link Para o Manul do Prawn clicável", :size => 10, :inline_format => true, :valign => :bottom, :align => :left# Inclui em baixo da folha do lado direito a data e o némero da página usando a tag pagepdf.number_pages "Gerado: #{(Time.now).strftime("%d/%m/%y as %H:%M")} - Página ", :start_count_at => 0, :page_filter => :all, :at => [pdf.bounds.right - 140, 7], :align => :right, :size => 8# Gera no nosso PDF e coloca na pasta public com o nome agreement.pdfpdf.render_file('public/agreement.pdf')endenddef self.spending spendings## Gráfico 1 ### Formata os dados para gerar o gráfico (Não se preocupe com isso, apenas saiba que nesse gráfico os dados de label precisa entrar como um hash)spending_labels = {}spendings.each_with_index {|s,i| spending_labels[i] = s[0].to_s}# Cria um objeto Gruff (gerador de gráfico)g = Gruff::AccumulatorBar.new 1000# Esconde a legendag.hide_legend = true# Escolhe o tamanho da Fontg.marker_font_size = 16# Escolhe as cores que serão usadasg.theme = {:colors => ['#aedaa9', '#12a702'],:marker_color => '#dddddd',:font_color => 'black',:background_colors => 'white'}# Aqui nós colocamos os dados y da tabelag.data 'Savings', spendings.map {|s| s[1]}# Aqui colocamos os dados que formatamos antes da coluna xg.labels = spending_labels# Gera a imagem no diretório público (você pode escolher onde gerar)g.write('public/graph.jpg')## Gráfico 2 ### Estamos colocando nossos dados direto em @datasets para preencher o gráfico 2@datasets = spendings# Cria o objeto Gruffg = Gruff::Pie.new 900g.theme = Gruff::Themes::PASTEL# Aqui ele formata nossos dados@datasets.each do |data|g.data(data[0], data[1])end# Aqui ele gera a imagem do gráficog.write("public/graph_pie.jpg")# Inicia nosso PDFPrawn::Document.new(PDF_OPTIONS) do |pdf|# Define a cor do traçadopdf.fill_color "666666"# Cria um título com tamanho 28 PDF Points, bold alinha no centropdf.text "Gráficos de gastos", :size => 28, :style => :bold, :align => :center# Move 60 PDF points para baixo o cursorpdf.move_down 60# Escreve mais um texto sobre o gráficopdf.text "Gráfico em R$ de gastos por setor", size: 14, color: 'AAAAAA', align: :center# Inclui a imagem com o gráfico na escala 0.50 para diminuir pela metade a imagempdf.image "public/graph.jpg", :scale => 0.50# Inclui em baixo da folha do lado direito a data e o numero da página usando a tag pagepdf.number_pages "Gerado: #{(Time.now).strftime("%d/%m/%y as %H:%M")} - Página ", :start_count_at => 0, :page_filter => :all, :at => [pdf.bounds.right - 140, 7], :align => :right, :size => 8# Muda de página para incluir o outro gráficopdf.start_new_page# Move 60 PDF points para baixo o cursorpdf.move_down 20# Escreve mais um texto sobre o gráficopdf.text "Gráfico em % de gastos por setor", size: 14, color: 'AAAAAA', align: :center# Incluir o gráfico numero 2pdf.image "public/graph_pie.jpg", :scale => 0.50# Inclui em baixo da folha do lado direito a data e o numero da página usando a tag pagepdf.number_pages "Gerado: #{(Time.now).strftime("%d/%m/%y as %H:%M")} - Página ", :start_count_at => 0, :page_filter => :all, :at => [pdf.bounds.right - 140, 7], :align => :right, :size => 8# Gera nosso pdf no diretório publicpdf.render_file('public/spending.pdf')endendend
Observe que temos 2 métodos dentro do módulo, o primeiro método (agreement) vai gerar nosso contrato usando os parâmetros que criarmos no scaffold de agreement usando o prawn e vai colocar o pdf criado em “public/agreement.pdf” o segundo (spending) cria o nosso PDF com dois gráficos baseados nos dados que entrarmos no scaffold de spendings usando o prawn para gerar o PDF e o gruff para gerar os gráficos (imagens) que serão incluídos dentro do PDF em “public/spending.pdf”. (Leia com calma os comentários do código acima) - Agora finalmente vamos testar nossa pequena aplicação, primeiro rode o seu servidor:
1rails s - No browser visite http://localhost:3000/agreements
- Agora ao lado de um Agreement já criado via form você dever ver um link escrito “export” como na imagem a baixo:
- Clique nele, o PDF vai abrir em uma nova aba e o resultado deve ser semelhante a este:
- Agora visite http://localhost:3000/spendings
- Crie alguns spendings para gerar dados para o gráfico como na imagem a baixo.
- Agora na mesma tela (listagem de spendings) clique no link “Export to Graph”, quando você fizer isso o PDF vai se abrir na aba ao lado e a primeira página deve ser semelhante a esta:
E a segunda página semelhante a está:
- Parabéns \o/ nós conseguimos criar um pequeno sistema que gera contratos e gráficos baseados nos dados das nossas tabelas.

Não perca nenhum conteúdo
Receba nosso resumo semanal com os novos posts, cursos, talks e vagas o/
CONCLUSÃO
Gerar PDF usando o ruby + prawn + gruff é uma tarefa muito simples e traz infinitas possibilidades, para você se aprofundar e aprender a fazer mais coisas com essas ferramentas incríveis acesse o manual do Prawn clicando aqui e o repositório do Gruff christian dating sites plenty of fish.
Como de costume o Código completo da aplicação está no Github, caso você queria clonar o código, myers briggs dating app Aproveita e me segue lá \o/
Se você ficou com dúvidas ou tem sugestões de posts para o Blog comenta aí em baixo ou me adiciona no Facebook new dating sites in canada.
Muito Obrigado,
Sua presença aqui é uma honra para mim,
Leonardo Scorza 🙂
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade, e em português, sobre programação com foco em Ruby on Rails e também JavaScript.
Além disso, aqui sempre levamos à você conteúdos valiosos sobre a carreira de programação, dicas sobre currículos, portfólios, perfil profissional, soft skills, enfim, tudo o que você precisa saber para continuar evoluindo como Programador(a)!
Fique por dentro de todos os conteúdos o/
Nossas redes sociais:
📹 • https://onebitcode.com/gay-gamers-dating/ [Live todas as terças-feiras às 19h)
💻 • https://linkedin.com/company/onebitcode
🙂 • https://facebook.com/onebitcode
📱 • https://instagram.com/one_bit_code
🐦 • https://twitter.com/onebitcode
Nossos cursos:
🥇 • Programador Full Stack Javascript em 8 Semanas
💎 • Curso Completo de Ruby
⚙ • Minicurso: API Rails 5 Completo
🐞 • Minicurso de Testes para Ruby on Rails com RSpec
Espero que curta nossos conteúdos e sempre que precisar de ajuda, fala com a gente!
Estamos aqui para você 🙂
Bem-vindo à família OneBitCode o/
Bom dia, ótimo post, só queria ressaltar, na linha 3 do “app/views/agreements/index.html.erb”, o “end” está no lugar errado. 🙂
Top como sempre!
Valeu o/