
Введение
В топике я рассмотрю создание 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!