Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle a file_as_string (generated by Prawn) so that it is accepted by Carrierwave?

I'm using Prawn to generate a PDF from the controller of a Rails app,

...
respond_to do |format|
  format.pdf do
    pdf = GenerateReportPdf.new(@object, view_context)
    send_data pdf.render, filename: "Report", type: "application/pdf", disposition: "inline"
  end
end

This works fine, but I now want to move GenerateReportPdf into a background task, and pass the resulting object to Carrierwave to upload directly to S3.

The worker looks like this

def perform
  pdf           = GenerateReportPdf.new(@object)
  fileString    = ???????
  document      = Document.new(
    object_id: @object.id,
    file: fileString )
    # file is field used by Carrierwave 
end

How do I handle the object returned by Prawn (?????) to ensure it is a format that can be read by Carrierwave.

fileString = pdf.render_file 'filename' writes the object to the root directory of the app. As I'm on Heroku this is not possible.

file = pdf.render returns ArgumentError: string contains null byte

fileString = StringIO.new( pdf.render_file 'filename' ) returns TypeError: no implicit conversion of nil into String

fileString = StringIO.new( pdf.render ) returns ActiveRecord::RecordInvalid: Validation failed: File You are not allowed to upload nil files, allowed types: jpg, jpeg, gif, png, pdf, doc, docx, xls, xlsx

fileString = File.open( pdf.render ) returns ArgumentError: string contains null byte

....and so on.

What am I missing? StringIO.new( pdf.render ) seems like it should work, but I'm unclear why its generating this error.

like image 216
Andy Harvey Avatar asked Jul 18 '15 04:07

Andy Harvey


2 Answers

It turns out StringIO.new( pdf.render ) should indeed work.

The problem I was having was that the filename was being set incorrectly and, despite following the advise below on Carrierwave's wiki, a bug elsewhere in the code meant that the filename was returning as an empty string. I'd overlooked this an assumed that something else was needed

https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Upload-from-a-string-in-Rails-3

my code ended up looking like this

def perform
  s = StringIO.new(pdf.render)

  def s.original_filename; "my file name"; end

  document  = Document.new(
    object_id: @object.id
  )

  document.file = s

  document.save!
end
like image 59
Andy Harvey Avatar answered Sep 24 '22 01:09

Andy Harvey


You want to create a tempfile (which is fine on Heroku as long as you don't expect it to persist across requests).

def perform
  # Create instance of your Carrierwave Uploader
  uploader = MyUploader.new

  # Generate your PDF
  pdf = GenerateReportPdf.new(@object)

  # Create a tempfile
  tmpfile = Tempfile.new("my_filename")

  # set to binary mode to avoid UTF-8 conversion errors
  tmpfile.binmode 

  # Use render to write the file contents
  tmpfile.write pdf.render

  # Upload the tempfile with your Carrierwave uploader
  uploader.store! tmpfile

  # Close the tempfile and delete it
  tmpfile.close
  tmpfile.unlink
end
like image 23
Stuart Gibson Avatar answered Sep 25 '22 01:09

Stuart Gibson