Существует много способов создания 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.
Я надеюсь, что этот урок оказался полезным. Пожалуйста, оставляйте любые вопросы, комментарии и отзывы в комментариях, и я буду рад продолжить.