Введение
В топике я рассмотрю создание PDF-документов средствами Ruby/Rails, их генерацию, скачивание напрямую с сайта или создание через rake task, а также возможность отправки во вложении через Mailer.
Работать будем с Rails 3.0.9 и gem'ом Prawn.
Установка Prawn
Bundler
gem 'prawn', :git => "git://github.com/sandal/prawn.git", :submodules => true
Интеграция с Рельсами
Создадим директорию для классов отчетов:
$ mkdir app/reports
Для автоподгрузки классов из этой директории в config/application.rb добавим:
config.autoload_paths << "#{Rails.root}/app/reports"
И зарегистрируем mime-тип (config/initializers/mime_types.rb)
Mime::Type.register_alias "application/pdf", :pdf
Включим gem в автозагрузку (предварительно создав файл) config/initializers/prawn.rb
require "prawn"
Проверим, работает ли Prawn (я использую RVM)
$ rails c
Loading development environment (Rails 3.0.9)
ruby-1.9.2-p180 :001 > Prawn::BASEDIR
=> "/home/kir/.rvm/gems/ruby-1.9.2-p180@pdfer/bundler/gems/prawn-1288242ddece"
Генерируем PDF
Чтобы Prawn поддерживал кириллицу, необходимо предоставить ему русские шрифты. Положим их в домашнюю директорию, у меня это /home/kir/prawn_fonts. «Вердану», проверенную в работе с Prawn, я выложил тут.
Чтобы иметь какую-нибудь таблицу с данными, создадим скаффолдер с моделью Customer.
rails g scaffold customer name:string amount:float
rake db:migrate
Наполним таблицу любыми данными.
Теперь можно создать файл-генератор отчета. Пусть это будет app/reports/customers_report.rb со следующим кодом:
# encoding: utf-8
class CustomersReport < Prawn::Document
# ширина колонок
Widths = [200, 200, 120]
# заглавия колонок
Headers = ['Дата добавления', 'Клиент', 'Баланс']
def row(date, customer_name, amount)
row = [date, customer_name, amount]
make_table([row]) do |t|
t.column_widths = Widths
t.cells.style :borders => [:left, :right], :padding => 2
end
end
def to_pdf
# привязываем шрифты
font_families.update(
"Verdana" => {
:bold => "/home/kir/prawn_fonts/verdanab.ttf",
:italic => "/home/kir/prawn_fonts/verdanai.ttf",
:normal => "/home/kir/prawn_fonts/verdana.ttf" })
font "Verdana", :size => 10
text "Отчет за #{Time.zone.now.strftime('%b %Y')}", :size => 15, :style => :bold, :align => :center
move_down(18)
# выборка записей
@customers = Customer.order('created_at')
data = []
items = @customers.each do |item|
data << row(item.created_at.strftime('%d/%m/%y %H:%M'), item.friendly_name, item.)
end
head = make_table([Headers], :column_widths => Widths)
table([[head], *(data.map{|d| [d]})], :header => true, :row_colors => %w[cccccc ffffff]) do
row(0).style :background_color => '000000', :text_color => 'ffffff'
cells.style :borders => []
end
# добавим время создания внизу страницы
creation_date = Time.zone.now.strftime("Отчет сгенерирован %e %b %Y в %H:%M")
go_to_page(page_count)
move_down(710)
text creation_date, :align => :right, :style => :italic, :size => 9
render
end
end
Генератор PDF готов. Добавим в контроллер Customers action, который будет отвечать за скачивание PDF'a.
def download_pdf
output = CustomersReport.new.to_pdf
send_data output, :type => 'application/pdf', :filename => "customers.pdf"
end
Не забудьте прописать route для этого экшена! Например, так:
resources :customers do
collection do
get 'download_pdf'
end
end
Переходим по адресу /customers/download_pdf. В результате получится вот такой документ.
rake task
Но, допустим, что мы хотим генерировать PDF через rake task. Рассмотрим реализацию (код lib/tasks/customers_report.rake):
namespace :customers_report do
desc 'Generates report with customers'
task :generate_pdf => :environment do
output = CustomersReport.new.to_pdf
filename = "report.pdf"
File.open(Rails.root.join('public', filename), 'wb') do |f|
f.write(output)
end
puts "Report was written to #{filename}"
end
end
Выполняем:
rake customers_report:generate_pdf --trace
Отчет будет генерироваться в public/report.pdf.
rake task + mailer
Еще одна ситуация: PDF надо отправлять ежемесячно на e-mail. Для этого нам понадобится создать mailer и еще один task.
Сгенерируем mailer:
rails g mailer customers_mailer report_email
и изменим код app/mailers/customers_mailer.rb:
class CustomersMailer < ActionMailer::Base
default :from => "me@yandexteam.ru" # укажите свой адрес!
def report_email(pdf_output, to)
report_filename = Time.zone.now.strftime('Report %d-%m-%Y')
attachments[report_filename] = {
:mime_type => 'application/pdf',
:content => pdf_output
}
mail(:to => to, :subject => report_filename.titleize)
end
end
И также прописываем task для его отправки (тот же lib/tasks/customers_report.rake):
namespace :customers_report do
desc 'Generates report with customers'
task :generate_pdf => :environment do
#...
end
desc 'Send report by email'
task :send_by_email => :environment do
# можно указывать массивом
# recipient = ["vasya@gmail.com", "petya@gmail.com"]
recipient = 'me@yandexteam.ru' # укажите свой адрес!
output = CustomersReport.new.to_pdf
CustomersMailer.report_email(output, recipient).deliver
puts "Report was sent to #{recipient}"
end
end
Готово. Не забудьте указать настоящие адреса и настройки action_mailer в параметрах окружения!
Проверяем работоспособность:
rake customers_report:send_by_email --trace
Ссылки:
Репозиторий Prawn на Github: https://github.com/sandal/prawn/
Мой репозиторий с демо-приложением, описанным в статье: https://github.com/kirs/pdfer
Об установке Prawn на Гитхабе: https://github.com/sandal/prawn/wiki/Using-Prawn-in-Rails
P.S. Большое спасибо Дмитрию Галинскому из evrone за апрельскую презентацию на Rails Club!