如何处理由Prawn生成的字符串文件,使其能够被Carrierwave接受?

13

我正在使用Prawn从Rails应用程序的控制器生成PDF文件。

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

worker看起来像这样

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' 将该对象写入应用的根目录。由于我在Heroku上,这不可能实现。

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 )似乎应该可行,但我不清楚为什么会生成此错误。

4个回答

12

事实证明,StringIO.new( pdf.render )确实可以工作。

我遇到的问题是文件名设置不正确,并且尽管遵循了Carrierwave的wiki中下面的建议,但代码中的另一个错误导致文件名返回为空字符串。我忽略了这一点并认为需要其他东西。

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

那你是如何解决代码中的“其他错误”呢?我也遇到了类似的问题。 - dax
@dax 我已经在我的答案中添加了代码。试一试并告诉我你的进展如何。 - Andy Harvey
谢谢,与此同时我在imgkit的这个示例中找到了解决方法,对我也有效。 - dax
有没有其他方法可以做到这一点?我需要将一个变量名传递给 original_name 函数,所以这种方法对我来说不太适用。谢谢。 - tomascharad

8
您想创建一个临时文件(在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

这个很好用!唯一的问题是在文件名的末尾添加了一个奇怪的哈希值,例如:[realFilename].pdf20160803-42256-1ja9w8d。 - tomascharad

0

这里有一种方法可以使用StringIO,就像Andy Harvey提到的那样,但不需要向StringIO实例的特殊类添加一个方法。

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

0
这个花了我几天时间,关键是调用render_file并控制文件路径,这样你就可以跟踪文件的情况,就像这样:
在我的一个模型中,比如Policy,我有一个文档列表,这只是与carrierwave连接的模型更新方法,比如:PolicyDocument < ApplicationRecord mount_uploader :pdf_file, PdfDocumentUploader
def upload_pdf_document_file_to_s3_bucket(document_type, filepath)
  policy_document = self.policy_documents.where(policy_document_type: document_type)
                        .where(status: 'processing')
                        .where(pdf_file: nil).last
  policy_document.pdf_file = File.open(file_path, "r")
  policy_document.status = 's3_uploaded'
  policy_document.save(validate:false)
  policy_document
  rescue => e
    policy_document.status = 's3_uploaded_failed'
    policy_document.save(validate:false)
    Rails.logger.error "Error uploading policy documents: #{e.inspect}"
  end
end

在我的一个Prawn PDF文件生成器中,例如:PolicyPdfDocumentX,请注意我如何呈现文件并返回文件路径,以便我可以从worker对象本身获取。
  def generate_prawn_pdf_document
    Prawn::Document.new do |pdf|
      pdf.draw_text "Hello World PDF File", size: 8, at: [370, 462]
      pdf.start_new_page
      pdf.image Rails.root.join('app', 'assets', 'images', 'hello-world.png'), width: 550
    end
  end

def generate_tmp_file(filename)
   file_path = File.join(Rails.root, "tmp/pdfs", filename)
   self.generate_prawn_pdf_document.render_file(file_path)
   return filepath
end

在“全局”工作器中创建文件并将它们上传到S3存储桶,例如:PolicyDocumentGeneratorWorker

def perform(filename, document_type, policy)
 #here we create the instance of the prawn pdf generator class
 pdf_generator_class = document_type.constantize.new
 #here we are creating the file, but also `returning the filepath`
 file_path = pdf_generator_class.generate_tmp_file(filename)
 #here we are simply updating the model with the new file created
 policy.upload_pdf_document_file_to_s3_bucket(document_type, file_path)
end

最后如何测试、运行rails c以及:

the_policy = Policies.where....
PolicyDocumentGeneratorWorker.new.perform('report_x.pdf', 'PolicyPdfDocumentX',the_policy)

注意:我正在使用元编程,以防我们有多个不同的文件生成器,constantize.new只是创建新的prawn pdf文档生成器实例,因此类似于PolicyPdfDocument.new,这样我们只需要一个pdf文档生成器工作类来处理所有您的prawn pdf文档,例如,如果您需要一个新文档,您可以简单地执行PolicyDocumentGeneratorWorker.new.perform('report_y.pdf', 'PolicyPdfDocumentY',the_policy)

:D

希望这能帮助某些人节省时间


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接