Существует много способов создания PDF-файлов в Ruby и Rails. Скорее всего, вы уже знакомы с HTML и CSS, поэтому мы собираемся использовать PDFKit для генерации PDF-файлов с использованием HTML из стандартного представления Rails и кода стилей.
Введение в PDFKit
Внутренне, PDFKit использует wkhtmltopdf (WebKit HTML to PDF), движок, который будет принимать HTML и CSS, визуализировать его с помощью WebKit и выводить его в виде PDF с высоким качеством.
Для начала установите wkhtmltopdf на свой компьютер. Вы можете загрузить бинарный файл или установить его с Brew на Mac или из своего предпочтительного репозитория Linux.
Вам также нужно установить pdfkit gem
, а затем запустить следующий бит Ruby, чтобы сгенерировать PDF с текстом «Hello Envato!»
1
2
3
4
5
6
7
|
require «pdfkit»
kit = PDFKit.new(<<-HTML)
<p>Hello Envato!</p>
HTML
kit.to_file(«hello.pdf»)
|
У вас должен быть новый файл с именем hello.pdf с текстом вверху.
PDFKit также позволяет создавать PDF из URL. Если вы хотите создать PDF-файл с главной страницы Google, вы можете запустить:
1
2
3
|
require «pdfkit»
PDFKit.new(‘https://www.google.com’, :page_size => ‘A3’).to_file(‘google.pdf’)
|
Как видите, я указываю page_size
— по умолчанию используется A4. Вы можете увидеть полный список вариантов здесь .
Стилизация PDF с использованием CSS
Ранее я упоминал, что мы собираемся генерировать файлы PDF с использованием HTML и CSS. В этом примере я добавил немного CSS для стилизации HTML для образца счета, как вы можете видеть:
01
02
03
04
05
06
07
08
09
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
|
require «pdfkit»
kit = PDFKit.new(<<-HTML)
<style>
* {
color: grey;
}
h1 {
text-align: center;
color: black;
margin-bottom: 100px;
}
.notes {
margin-top: 100px;
}
table {
width: 100%;
}
th {
text-align: left;
color: black;
padding-bottom: 15px;
}
</style>
<h1>Envato Invoice</h1>
<table>
<thead>
<tr>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Monthly Subscription to Tuts+</td>
<td>$15</td>
</tr>
</tbody>
</table>
<div class=»notes»>
<p><strong>Notes:</strong> This invoice was paid on the 23rd of March 2016 using your credit card ending on 1234.</p>
</div>
HTML
kit.to_file(«envato_invoice.pdf»)
|
Если вы запустите этот скрипт, будет сгенерирован файл envato_invoice.pdf . Эта фотография показывает результат образца счета:
Как видите, PDFKit очень прост в использовании, если вы уже знакомы с HTML и CSS. Вы можете продолжить настройку или стилизацию этого документа по своему усмотрению.
Использование PDFKit из приложения Rails
Теперь давайте посмотрим, как использовать PDFKit в контексте приложения Rails, чтобы мы могли динамически генерировать PDF-файлы, используя данные из наших моделей. В этом разделе мы собираемся создать простое приложение rails для динамической генерации предыдущего «счета-фактуры Envato». Начните с создания нового приложения rails и добавления трех моделей:
1
2
3
4
5
6
7
|
$ rails new envato_invoices
$ cd envato_invoices
$ rails generate model invoice date:date client notes
$ rails generate model line_item description price:float invoice:references
$ rake db:migrate
|
Теперь нам нужно добавить несколько примеров данных в базу данных. Добавьте этот фрагмент кода в db/seeds.rb
.
1
2
3
4
5
6
7
8
|
line_items = LineItem.create([
{ description: ‘Tuts+ Subscription April 2016’, price: 15.0 },
{ description: ‘Ruby eBook’, price: 9.90} ])
Invoice.create(
client: ‘Pedro Alonso’,
total: 24.90,
line_items: line_items,
date: Date.new(2016, 4, 1))
|
Запустите rake db:seed
в вашем терминале, чтобы добавить образец счета в базу данных.
Мы также заинтересованы в создании списка счетов-фактур и детализации одного счета-фактуры в нашем приложении, поэтому, используя rails Generator, запустите rails generate controller Invoices index show
для создания контроллера и представлений.
приложение / контроллеры / invoices_controller.rb
1
2
3
4
5
6
7
8
9
|
class InvoicesController < ApplicationController
def index
@invoices = Invoice.all
end
def show
@invoice = Invoice.find(params[:id])
end
end
|
приложение / просмотров / счета / index.html.erb
1
2
3
4
5
6
7
8
|
<h1>Invoices</h1>
<ul>
<% @invoices.each do |invoice|
<li>
<%= link_to «#{invoice.id} — #{invoice.client} — #{invoice.date.strftime(«%B %d, %Y»)} «, invoice_path(invoice) %>
</li>
<% end %>
</ul>
|
Нам нужно изменить маршруты rails, чтобы по умолчанию перенаправлять их на InvoicesController
, поэтому отредактируйте config/routes.rb
:
1
2
3
4
5
|
Rails.application.routes.draw do
root to: ‘invoices#index’
resources :invoices, only: [:index, :show]
end
|
Запустите ваш rails server
и перейдите к localhost: 3000, чтобы увидеть список счетов:
приложение / просмотров / счета / show.html.erb
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<div class=»invoice»>
<h1>Envato Invoice</h1>
<h3>To: <%= @invoice.client %></h3>
<h3>Date: <%= @invoice.date.strftime(«%B %d, %Y») %></h3>
<table>
<thead>
<tr>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<% @invoice.line_items.each do |line_item|
<tr>
<td><%= line_item.description %></td>
<td><%= number_to_currency(line_item.price) %></td>
</tr>
<% end %>
<tr class=»total»>
<td style=»text-align: right»>Total: </td>
<td><%= number_to_currency(@invoice.total) %>
</tr>
</tbody>
</table>
<% if @invoice.notes %>
<div class=»notes»>
<p><strong>Notes:</strong> <%= @invoice.notes %></p>
</div>
<% end %>
</div>
|
CSS для этой страницы сведений о счете был перемещен в app / assets / stylesheets / application.scss
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
.invoice {
width: 700px;
max-width: 700px;
border: 1px solid grey;
margin: 50px;
padding: 50px;
h1 {
text-align: center;
margin-bottom: 100px;
}
.notes {
margin-top: 100px;
}
table {
width: 90%;
text-align: left;
}
th {
padding-bottom: 15px;
}
.total td {
font-size: 20px;
font-weight: bold;
padding-top: 25px;
}
}
|
Затем, когда вы щелкнете по счету на главной странице листинга, вы увидите детали:
На данный момент мы готовы добавить функциональность в наше приложение rails для просмотра или загрузки счетов в формате PDF.
InvoicePdf Класс для обработки рендеринга PDF
Чтобы отобразить счета из нашего приложения rails в PDF, нам нужно добавить в Gemfile три гема: PDFKit , render_anywhere и wkhtmltopdf-binary. По умолчанию рельсы позволяют отображать шаблоны только из контроллера, но с помощью render_anywhere
мы можем отображать шаблон из модели или фонового задания.
1
2
3
|
gem ‘pdfkit’
gem ‘render_anywhere’
gem ‘wkhtmltopdf-binary’
|
Чтобы не загрязнять наши контроллеры слишком большой логикой, я собираюсь создать новый класс InvoicePdf
внутри папки app/models
чтобы обернуть логику для создания PDF.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
require «render_anywhere»
class InvoicePdf
include RenderAnywhere
def initialize(invoice)
@invoice = invoice
end
def to_pdf
kit = PDFKit.new(as_html, page_size: ‘A4’)
kit.to_file(«#{Rails.root}/public/invoice.pdf»)
end
def filename
«Invoice #{invoice.id}.pdf»
end
private
attr_reader :invoice
def as_html
render template: «invoices/pdf», layout: «invoice_pdf», locals: { invoice: invoice }
end
end
|
Этот класс просто принимает счет для отображения в качестве параметра конструктора класса. as_html
метод as_html
является чтение шаблона представления layout_pdf
invoices/pdf
и layout_pdf
который мы используем для генерации HTML-кода, который нам нужно представить в формате PDF. Наконец, метод to_pdf
использует PDFKit для сохранения файла PDF в общей папке rails.
Возможно, вы хотите создать динамическое имя в вашем реальном приложении, чтобы PDF-файл не был перезаписан случайно. Возможно, вы захотите сохранить файл в AWS S3 или в личной папке, но это выходит за рамки данного руководства.
/app/views/invoices/pdf.html.erb
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<div class=»invoice»>
<h1>Envato Invoice</h1>
<h3>To: <%= invoice.client %></h3>
<h3>Date: <%= invoice.date.strftime(«%B %d, %Y») %></h3>
<table>
<thead>
<tr>
<th>Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<% invoice.line_items.each do |line_item|
<tr>
<td><%= line_item.description %></td>
<td><%= number_to_currency(line_item.price) %></td>
</tr>
<% end %>
<tr class=»total»>
<td style=»text-align: right»>Total: </td>
<td><%= number_to_currency(invoice.total) %>
</tr>
</tbody>
</table>
<% if invoice.notes %>
<div class=»notes»>
<p><strong>Notes:</strong> <%= invoice.notes %></p>
</div>
<% end %>
</div>
|
/app/views/layouts/invoice_pdf.erb
01
02
03
04
05
06
07
08
09
10
11
12
|
<!DOCTYPE html>
<html>
<head>
<title>Envato Invoices</title>
<style>
<%= Rails.application.assets.find_asset(‘application.scss’).to_s %>
</style>
</head>
<body>
<%= yield %>
</body>
</html>
|
В этом файле макета следует обратить внимание на то, что мы визуализируем стили в макете. WkHtmlToPdf работает лучше, если мы визуализируем стили таким образом.
ЗагрузкиКонтроллер для рендеринга счета в формате PDF
На этом этапе нам нужен маршрут и контроллер, который вызывает класс InvoicePdf
для отправки PDF-файла в браузер, поэтому отредактируйте config/routes.rb
чтобы добавить вложенный ресурс:
1
2
3
4
5
6
7
|
Rails.application.routes.draw do
root to: «invoices#index»
resources :invoices, only: [:index, :show] do
resource :download, only: [:show]
end
end
|
Если мы запускаем rake routes
, мы видим список маршрутов, доступных в приложении:
1
2
3
4
5
|
Prefix Verb URI Pattern Controller#Action
root GET / invoices#index
invoice_download GET /invoices/:invoice_id/download(.:format) downloads#show
invoices GET /invoices(.:format) invoices#index
invoice GET /invoices/:id(.:format) invoices#show
|
Добавьте app/controllers/downloads_controller.rb
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class DownloadsController < ApplicationController
def show
respond_to do |format|
format.pdf { send_invoice_pdf }
end
end
private
def invoice_pdf
invoice = Invoice.find(params[:invoice_id])
InvoicePdf.new(invoice)
end
def send_invoice_pdf
send_file invoice_pdf.to_pdf,
filename: invoice_pdf.filename,
type: «application/pdf»,
disposition: «inline»
end
end
|
Как вы можете видеть, когда запрос запрашивает файл PDF, метод send_invoice_pdf
обрабатывает запрос. Метод invoice_pdf
просто находит счет-фактуру из базы данных по идентификатору и создает экземпляр InvoicePdf. Затем send_invoice_pdf
просто вызывает метод to_pdf
, чтобы отправить сгенерированный файл PDF в браузер.
Стоит отметить, что мы передаем расположение параметра disposition: "inline"
в send_file
. Этот параметр отправляет файл в браузер, и он будет отображен. Если вы хотите принудительно загрузить файл, тогда вам нужно будет передать disposition: "attachment"
.
Добавьте кнопку загрузки в ваш шаблон шоу- app/views/invoices/show.html.erb
:
1
2
3
4
|
<%= link_to «Download PDF»,
invoice_download_path(@invoice, format: «pdf»),
target: «_blank»,
class: «download» %>
|
Запустите приложение, перейдите к деталям счета-фактуры, нажмите «Загрузить», и откроется новая вкладка, отображающая счет-фактуру в формате PDF.
Визуализация PDF как HTML в разработке
Когда вы работаете над разметкой для своего PDF, необходимость генерировать PDF каждый раз, когда вы хотите проверить изменение, иногда может быть медленной. По этой причине возможность просмотра HTML-кода, который будет преобразован в формат PDF, может быть очень полезной. Нам нужно только отредактировать /app/controllers/downloads_controller.rb
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class DownloadsController < ApplicationController
def show
respond_to do |format|
format.pdf { send_invoice_pdf }
if Rails.env.development?
format.html { render_sample_html }
end
end
end
private
def invoice
Invoice.find(params[:invoice_id])
end
def invoice_pdf
InvoicePdf.new(invoice)
end
def send_invoice_pdf
send_file invoice_pdf.to_pdf,
filename: invoice_pdf.filename,
type: «application/pdf»,
disposition: «inline»
end
def render_sample_html
render template: «invoices/pdf», layout: «invoice_pdf», locals: { invoice: invoice }
end
end
|
Теперь метод show
также отвечает на запросы HTML в режиме разработки. Маршрут для счета в формате PDF будет выглядеть примерно так : http: // localhost: 3000 / invoices / 1 / download.pdf . Если вы измените его на http: // localhost: 3000 / invoices / 1 / download.html , вы увидите счет в HTML с использованием разметки, которая используется для создания PDF.
Учитывая приведенный выше код, генерация PDF-файлов с использованием Ruby on Rails проста, если вы знакомы с языком Ruby и инфраструктурой Rails. Возможно, самым приятным аспектом всего процесса является то, что вам не нужно изучать какие-либо новые языки разметки или особенности создания PDF.
Я надеюсь, что этот урок оказался полезным. Пожалуйста, оставляйте любые вопросы, комментарии и отзывы в комментариях, и я буду рад продолжить.