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.