Tech Insights
Asitha Bandara
March 6, 2020

Export PDFs in a Ruby on Rails project

Export PDFs in a Ruby on Rails project

Asitha Bandara

Today's blog post isabout exporting PDFs in a Ruby on Rails project. We will discuss how to achievethe following tasks:

1.       Generatecontent as PDF

2.     Handle thePDF generation as a background job

3.     Make thelink available in an email for download purposes

Generate content as PDF

For this section, thefollowing Gem is used out of many libraries which does the same thing.

gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'

If you want to take alook inside this library here is the link.

This awesome librarymakes PDF generation easy. These are the steps that were followed:

1. Add theGem into the file and install it.

bundle install

2. Change the required method with respond_to block.

  respond_to do |format|
     format.html
     format.pdfdo
       html =render_to_string template: "pdf/diary.pdf"
       folder ="#{@diary.id}_#{@diary.child_name}_#{@year}"
       file_name= "#{@diary.child_name}"
       
       pdf =WickedPdf.new.pdf_from_string(html)
       
       file_name= "#{file_name}-#{year}"
       save_path=Rails.root.join("public/export/#{folder}","#{file_name}.pdf")
       
      File.open(save_path, 'wb') do |file|
         file<< pdf
       end
       
      redirect_to finish_download_content_index_path, notice: t('download_link_will_be_sent_to_your_email')
      end
    end

For this, a separatetemplate for the PDF generation had to be created and it is referenced in,

html = render_to_string template: "PDF template file"

Those files are added ina folder named “PDF” under the view directory as below:

         

Wicked PDF librarygenerates the PDF from the HTML string. So we can parse the string into thewicked PDF library as follows.

pdf = WickedPdf.new.pdf_from_string(html)

html refers to thestring that is rendered from the template.

That’s all here; butthere is a slight issue when this string is huge. It will hold the server untilit completes the PDF generation. Therefore this process needs to be in thebackground, so that it’ll generate the PDF without interrupting the mainthread.

How to handle PDF generation asa background job?

For this, the RailsActive Job facility can be used. First, create a class to perform thisoperation.

To create a new job:

rails generate job pdfs_generate

Those jobs will be savedin a new folder named "Jobs”.

For the Job execution aqueuing library is required. There are many such libraries. Some examples arebelow.

·        Sidekiq

·        Resque

·        Sneakers

·        Sucker Punch

·        Queue Classic

·        Delayed Job

Let's use the “DelayedJob” library:

gem'delayed_job_active_record'

It is time to embed thewicked PDF generation process inside the Job class now.

Note:

There are two mainthings in a JOB.

1. Perform method

2. Call backs →after_perform, before_perform etc.

Perform method willcarry out the process in the background. Once it is completed, it will call the"callback method" named as “after_perform”, which will be used tosend the email after the PDF generation process.

Here is the PDF generateJob class file.

class PdfsGenerateJob< ActiveJob::Base
 queue_as:default

 after_perform do|job|
   puts"****************************************"
   puts"************** DONE !!!!!!! *************"
   puts"****************************************"
   puts"Email should sent to : #{@user_email}"
   puts"Diary Id:  #{@diary_id}"
   puts"****************************************"
   
   # Sending theemail
  DownloadMailer.send_download_pdf_link(@user_email, @folder,@file_name).deliver_now
 end

 defperform(html, folder, file_name, user_email, year, diary, year_start_date,year_end_date)
   pdf =WickedPdf.new.pdf_from_string(html)
   file_name ="#{file_name}-#{year}"
   save_path = Rails.root.join("public/export/#{folder}","#{file_name}.pdf")
  File.open(save_path, 'wb') do |file|
     file<< pdf
   end
   @user_email =user_email
   @diary_id =diary.id
   @folder =folder
   @file_name =file_name
 end
end

Show method was changedby getting the perform function in a new thread in order to call the Jobprocess and to call the perform function using the later postfix.

  respond_to do |format|
     format.html
     format.pdfdo
       Thread.newdo
         html =render_to_string template: "pdf/diary.pdf"
         folder ="#{@diary.id}_#{@diary.child_name}_#{@year}"
        file_name = "#{@diary.child_name}"

        PdfsGenerateJob.perform_later(html, folder, file_name,@current_user.email, @year, @diary, @year_start_date, @year_end_date)
       end
       redirect_tofinish_download_content_index_path, notice:t('download_link_will_be_sent_to_your_email')
      end
    end

Wicked PDF generationwill happen in a new thread in the background. Therefore, it won't affect themain rails process. User is free to do any other work.

rails generate jobguests_cleanup

According to this code,once the PDF work is completed, it will send the email to the current user asdescribed above. (remind → after_perform callback).

Hope the process isclear to you. Please feel free to comment below if you have any questions.