Search code examples
ruby-on-rails-3herokuwicked-pdfrubyzip

generate ZIP from generated PDFs with wicked_pdf


In my invoice system, I want a backup function to download all invoices at once in one zip file. This system is running on heroku - so it's only possible to save the pdfs temporary.

I've the rubyzip and wicked_pdf gem installed.

My current code in the controller:

  def zip_all_bills
    @bill = Bill.all
    if @bill.count > 0
      t = Tempfile.new("bill_tmp_#{Time.now}")
      Zip::ZipOutputStream.open(t.path) do |z|
        @bill.each do |bill|
          @bills = bill
          @customer = @bills.customer
          @customer_name = @customer.name_company_id
          t = WickedPdf.new.pdf_from_string(
              render :template => '/bills/printing.html.erb',
                     :disposition => "attachment",
                     :margin => { :bottom => 23 },
                     :footer => { :html => { :template => 'pdf/footer.pdf.erb' } }
          )

          z.puts("invoice_#{bill.id}")
          z.print IO.read(t.path)
        end
      end

      send_file t.path, :type => "application/zip",
                        :disposition => "attachment",
                        :filename => "bills_backup"

      t.close
    end

    respond_to do |format|
      format.html { redirect_to bills_url }
    end
  end

This ends with the message IOError in BillsController#zip_all_bills closed stream


Solution

  • I think what is wrong in your code is that you have t for your zip but you also also use it for individual pdfs. So I think when you try to use the tempfile for a pdf it is a problem because you are already using it for the zip.

    But I don't think you need to use tempfiles at all (and I never actually got a tempfile solution working on Heroku)

    Here is a controller method that works for me--also using wickedpdf and rubyzip on heroku. Note that I'm not using a Tempfile instead I'm just doing everything with StringIO (at least I think that's the underlying tech).

    def dec_zip
      require 'zip'
      #grab some test records
      @coverages = Coverage.all.limit(10)
      stringio = Zip::OutputStream.write_buffer do |zio|
          @coverages.each do |coverage|
            #create and add a text file for this record
            zio.put_next_entry("#{coverage.id}_test.txt")
            zio.write "Hello #{coverage.agency_name}!"
            #create and add a pdf file for this record
            dec_pdf = render_to_string :pdf => "#{coverage.id}_dec.pdf", :template => 'coverages/dec_page', :locals => {coverage: coverage}, :layout => 'print'
            zio.put_next_entry("#{coverage.id}_dec.pdf")
            zio << dec_pdf
          end
        end
        # This is needed because we are at the end of the stream and 
        # will send zero bytes otherwise
        stringio.rewind
        #just using variable assignment for clarity here
        binary_data = stringio.sysread
        send_data(binary_data, :type => 'application/zip', :filename => "test_dec_page.zip")
    end