Reputation: 3860
I have a scenario where i need to generate a multipage pdf based on an array of objects.
Following is my models:
# Member has_many certificates
class Member < ActiveRecord::Base
has_many :certificates, dependent: :destroy
before_save :set_expiry
def self.delete_expired
where(expire_at: Date.today).destroy_all
end
private
def set_expiry
self.expire_at = 15.days.from_now
end
end
# certificate belongs_to member
class Certificate < ActiveRecord::Base
belongs_to :member
belongs_to :certificate_type
belongs_to :company
def company_name
company.name
end
def page_orientation
company_name == 'CompanyA' ? 'Landscape' : 'Portrait'
end
def page_height
company_name == 'CompanyA' ? '10.5in' : '11in'
end
def page_width
company_name == 'CompanyA' ? '7in' : '8.5in'
end
end
# each type can have many certificates
class CertificateType < ActiveRecord::Base
has_many :certificates
end
The user flow is something like following:
I have a custom form where i'm creating certificate objects (checkbox selection) and then submitting the form. The controller receives a hash of certificate objects. I have to loop through all the objects one by one to create the certificate for it's associated member. The problem is that each certificate has it's own template and some custom page layout settings like orientation, width and height. I want to generate one single pdf file with multiple pages where each page is a single certificate. I'm stuck at where and how should i pass the page layout settings for each individual certificate. I tried render_to_string in my controller and tried to set page layout options there but it doesn't seem to work. I need some suggestion or pointers here.
Upvotes: 2
Views: 3270
Reputation: 236
In my case I wanted to generate multiple PDFs in a background job and save the final merged file as an attachment in my model. So I did it like this:
def generate_and_send
begin
final_pdf = pdf_from_html.to_pdf
FileUtils.mkdir_p(File.dirname(pdf_path)) unless Dir.exists?(File.dirname(pdf_path))
File.open(pdf_path, 'wb') do |file|
file << final_pdf
end
@object.attachment = File.open pdf_path
@object.save
rescue => e
notifiy_status('error')
end
And my pdf generator and merger was set-up like this
def pdf_from_html
#Will generate each page in separate to prevent memory overflow
#Then joins the pages
final_pdf = CombinePDF.new
pages = @pages_to_print.select{ |k, v| v == '1'}.keys
pages.each do |page|
page_html = render("#{page}") #This method defines the layout based on the page
pdf_from_page = WickedPdf.new.pdf_from_string(page_html)
final_pdf << CombinePDF.parse(pdf_from_page, allow_optional_content: true)
end
final_pdf
end
Tips: Remember to use the method .to_pdf
Upvotes: 1
Reputation: 19221
Building on @fdisk 's answer, but using the Ruby native combine_pdf
gem... you could try:
require 'combine_pdf'
merged_certificates = CombinePDF.new
@member.certificates.each do |certificate|
pdf_data = WickedPdf.new.pdf_from_string(render_to_string(something, layout: some_layout))
merged_certificates << CombinePDF.parse(pdf_data)
end
send_data merged_certificates.to_pdf, filename: 'mypdf.pdf' #...
Upvotes: 5
Reputation: 438
Since this is an option, I would recommend installing poppler-utils
via your distro's package manager (yum
, apt-get
, dnf
, etc.). After that, save each certificate as a Tempfile
:
require 'shellwords'
certificates = @member.certificates.map do |certificate|
Tempfile.open(["cert", ".pdf"]) { |file| file << WickedPdf.new.pdf_from_string(render_to_string(something, layout: some_layout)) }
end
paths = certificates.map { |cert| cert.path.shellescape }
@final_pdf = Tempfile.new(["cert", ".pdf"])
@final_pdf.close
%x[pdfunite #{paths} #{@final_pdf.path.shellescape}]
That @final_pdf
object should contain the merged individual certificates. I've also made a simple gem to handle these cases, but it may have a bit of extra overhead you're not looking for (it's designed to create combined PDF files with nested bookmarks).
Upvotes: 0