Как обрабатывать file_as_string (сгенерированный Prawn), чтобы он принимался Carrierwave?

Я использую Prawn для создания PDF файла из контроллера приложения Rails,

...
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

Это отлично работает, но теперь я хочу переместить GenerateReportPdf в фоновое задание и передать результирующий объект Carrierwave для прямой загрузки на S3.

Рабочий выглядит так:

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

Как обработать объект, возвращенный Prawn (?????), чтобы убедиться, что это формат, который может быть прочитан Carrierwave.

fileString = pdf.render_file 'filename' записывает объект в корневую директорию приложения. Поскольку я на Хереку, это невозможно.

file = pdf.render возвращает ArgumentError: string contains null byte

fileString = StringIO.new( pdf.render_file 'filename' ) возвращает TypeError: no implicit conversion of nil into String

fileString = StringIO.new( pdf.render ) возвращает 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 ) возвращает ArgumentError: string contains null byte

.... и т.д.

Что мне не хватает? StringIO.new( pdf.render ) кажется, что он должен работать, но я не понимаю, почему он порождает эту ошибку.

Ответ 1

Оказывается, StringIO.new( pdf.render ) действительно должен работать.

Проблема, с которой я столкнулась, заключалась в том, что имя файла было задано некорректно и, несмотря на то, что ниже приведено описание на вики Carrierwave, ошибка в другом месте этого кода означала, что имя файла возвращалось как пустая строка. Я бы забыл об этом, предположил, что нужно что-то еще.

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

мой код выглядел так:

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

Ответ 2

Вы хотите создать временный файл (который отлично подходит для Heroku, если вы не ожидаете, что он будет сохраняться через запросы).

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

Ответ 3

Здесь вы можете использовать StringIO, как упомянул Энди Харви, но не добавляя метод к электограмме StringIO intstance.

class VirtualFile < StringIO
  attr_accessor :original_filename

  def initialize(string, original_filename)
    @original_filename = original_filename
    super(string)
  end
end

def perform
  pdf_string    = GenerateReportPdf.new(@object)
  file          = VirtualFile.new(pdf_string, 'filename.pdf')
  document      = Document.new(object_id: @object.id, file: file)
end