Rails PDF Reports recipe
Prawn
Prawn is a direct PDF generation ruby library, with wide usage and good documentation support.
Step 1 - Add Dependencies to gemfile
gem 'prawn'
gem 'prawn-tables'
run bundle to install the gems
$ bundle install
Step 2 – Register the PDF MIME Type in the initializer
# file: rails-generate-pdf/config/initializer/mime_types.rb
Mime::Type.register “application/pdf”, :pdf
Step 3 - create a template for your pdf’s
create a directory in your app folder to store your pdf’s.
class PdfTemplate < Prawn::Document
TABLE_ROW_COLORS = ["FFFFFF","DDDDDD"]
TABLE_FONT_SIZE = 9
TABLE_BORDER_STYLE = :grid
INFO = {
:Creator => "JourneyCTRL App",
:Producer => "Prawn",
:CreationDate => Time.now}
PDF_OPTIONS = {
:info => INFO,
:page_size => "A4",
:page_layout => :portrait,
:margin => [40, 75]
}
def initialize(default_options={})
default_options ? default_prawn_options = default_options : default_prawn_options = PDF_OPTIONS
super(default_prawn_options)
font_size 10
end
def header(title=nil)
text "JourneyCTRL", size: 15, style: :bold, align: :left
if title
text title, size: 14, style: :bold_italic, align: :left
end
move_up 12
text "Date: #{ApplicationController.helpers.format_date_time(Time.now)}", style: :normal, align: :right
move_down 7
stroke_horizontal_rule
move_down 10
end
def footer
# ...
end
end
Create your pdf’s
class AccountabilitySummaryReport < PdfTemplate
TABLE_WIDTHS = [120, 120, 100, 80, 80]
EXPENSE_HEADERS = ["Category", "Payment Mode", "Value", "Currency"]
def initialize(acc)
super()
@acc = acc
@exp = ExpenseSummary.where(...)
header 'Accountability Summary Report'
display_user_info
display_expenses
display_balance
#footer
end
private
def display_user_info
text "Responsible: #{}", size: 11, style: :normal, align: :left
move_down 5
end
def display_expenses
text "Expenses:", size: 12, style: :bold, align: :left
@expense_data = [EXPENSE_HEADERS]
@expense_data += @exp.map { |e| [e.expense_category_name,
e.payment_mode_name,
e.value,
e.currency] }
total = 0
@total_expenses = @exp.map{ |e| total += e.value}
@expense_data += [[{content: "Total", colspan: 2}, @acc.payment_mode_name, total, @acc.currency]]
table @expense_data,
header: true,
column_widths: TABLE_WIDTHS,
row_colors: TABLE_ROW_COLORS do
row(0).font_style = :bold
end
move_down 10
end
def display_balance
...
end
end
Rails integration
in your controller:
def show
respond_to do |format|
format.js
format.pdf do
if params[:tipo] == "summary"
pdf = AccountabilitySummaryReport.new(acc)
else
pdf = AccountabilityDetailReport.new(acc)
end
send_data pdf.render,
filename: "accountability_sumary_report_#{current_user.id}.pdf",
type: 'application/pdf',
disposition: 'inline'
end
end
end
Calling your controller:
link_to accountability_report_path(acc, format: "pdf", :tipo => "summary"),
target: "_blank",
:class => 'btn btn-primary btn-sm',
title: "Print",
data: { toggle: 'tooltip', placement: 'top' }
Testing
Install the pdf-reader gem https://github.com/yob/pdf-reader, that can convert the renderred PDF into a string and assert that the proper content is included.
RSpec:
describe AccountabilitySummaryReport do
context 'Given an that there is a single expense' do
let(:accountabilities) { [{id: 1, currency: "BRL", value: 100, payment_mode_name: 'CC', expense_category_name: 'Lunch' ] }
context 'The rendered pdf content' do
let(:pdf) { AccountabilitySummaryReport.new(accountabilities) }
let(:pdf_content) { PDF::Reader.new(StringIO.new(pdf.render)).page(1).to_s }
it 'contains the category of the expense' do
expect(pdf_content).to include('Lunch')
end
it 'contains the expense currency' do
expect(pdf_content).to include('BRL')
end
end
end
end
References
Prawn Manual Prawn-tables manual
Other options
wkhtmltopdf and wicked_pdf:
https://www.pdftron.com/blog/rails/how-to-generate-pdf-with-ruby-on-rails/