Home > Back-end >  Rails 7 download PDF from binary string
Rails 7 download PDF from binary string

Time:09-27

In my Rails 7 app to PDF creation I'm using external service. To get PDF I have to send a GET request and in the response I'm receiving encoded string. Now I would like to give the possibility to download this file without saving it on the server.

How to do so? all searched topics are almost 10y old, is it some modern way (or maybe a gem) to do so ?

Everything I found actually boils down to the code below:

# service which I'm using to create generate pdf from string
class PdfGenerator
  def initialize(binary_pdf)
    @binary_pdf = binary_pdf
  end

  attr_reader :binary_pdf
  
  def call
    File.open('invoice.pdf', 'wb') do |file|
      content = Base64.decode64(binary_pdf)
      file << content
    end
  end
end

# payments_controller.rb
  def pdf_download
    response = client.invoice_pdf(payment_id: params[:id]) # get request to fetch pdf
    PdfGenerator.new(response[:invoice_pdf]).call
  end

View where I'm hitting pdf_download endpoint:

<%= link_to t('.pdf_download'), pdf_download_payment_path(payment.id), data: { 'turbo-method' => :post } %>

I expected to start downloading the file but nothing like that happened. In rails server I'm receiving below message instead:

No template found for PaymentsController#pdf_download, rendering head :no_content Completed 204 No Content in 8ms (ActiveRecord: 0.2ms | Allocations: 2737)

CodePudding user response:

My answer is mostly a guess as working with files is not one of my strengths.

But basically because you have no escape in payments_controller#pdf_download such as render, redirect etc .. your application is expecting a view called "pdf_download" in views/payments folder.

In your controller, you have to send the data to the user, and this should fix the problem:

send_data(file, disposition: 'inline', filename: "invoice.pdf", type: 'application/pdf')

or

send_data(PdfGenerator.new(response[:invoice_pdf]).call, disposition: 'inline', filename: "invoice.pdf", type: 'application/pdf')

in your case. (The call action should return the file, if it doesn't be more explicit with a return statement)

Though one thing : you create invoices from the same file "invoice.pdf" at the root of your app. Because of GIL/GVL only one thread is performing Ruby at a single time then your file will be created in one go, and returned to the user the same way, without any issue.

Though in case you change the way your app work, maybe introducing async, then race may happen where multiple threads are modifying the same single file at the same time. You may then switch to using tempfiles with different names or finding a way to make your invoices all distincts.

  • Related