4

I have a large number of products I want to display in a pdf, with category headers. If a category doesn't fit on the current page I want to move it to the next. For this I'm using Prawn's group method.

Product.all.group_by(&:category).each do |category, products|
  pdf.group do
    # Simplified the data a bit for this example
    pdf.text category
    pdf.table products.map{ |p| [p.title, p.price] }
  end
end

This works very well for small amounts of products, but when I add more than 100 or so it takes a very long time and then ends in "failed to allocate memory". If I don't use the group method it takes about 30 seconds.

Clearly the group method does not manage its memory usage very well. Any suggestions for workarounds would be appreciated.

Peter Duijnstee
  • 3,759
  • 2
  • 20
  • 30
  • After some more searching through SO I thought I found a workaround that would fill my needs: http://stackoverflow.com/questions/2081635. Unfortunately using this method has the exact same problem: after a while the process starts taking up 100% cpu and eventually results in "failed to allocate memory" – Peter Duijnstee Nov 18 '10 at 19:17
  • I still would like to have an answer to this, so I decided to add a bounty. Hey it's worth a shot. – Peter Duijnstee Dec 05 '10 at 20:58
  • I am having the same problem with Prawn and the group method. It looks like group works OK until about the 50th group in a PDF. At that point, it takes a long time to iterate through subsequent groups. – Teddy Mar 24 '11 at 19:32

2 Answers2

2

I was using prawn on one project, sorry to tell that, but it was an disaster, finally we had to switch to wicked pdf. I advice you to do the same before you do not have to much code.

mpapis
  • 52,729
  • 14
  • 121
  • 158
  • Thanks for the suggestion, I am starting to think the same thing. Unfortunately I need very precise formatting based on an existing print-only design. Wicked pdf I think uses html conversion. I am looking into other options like pdf-writer though. – Peter Duijnstee Nov 18 '10 at 15:17
  • I suppose no better answers are going to be forthcoming. I decided to switch over to pdf-writer although it's slow going. It really is a shame Prawn doesn't handle this stuff well because syntax wise it's by far the most convenient PDF library I've come across. – Peter Duijnstee Nov 23 '10 at 19:02
1

---------- UPDATED ANSWER --------

The previous workaround wasn't good enough for the production server, so I had to use development version from git repo installed as a submodule under vendor/prawn, as described here: https://github.com/sandal/prawn/wiki/Using-Prawn-in-Rails

The memory issue with the group method is gone, but the syntax/options for things have changed somewhat. So I had to rewrite the code to generate PDF.

Also, getting the submodule to play nicely with the git repo for the Rails app is difficult. Deployment to production was tough.

---------- ORIGINAL ANSWER --------

This isn't a fix, but it makes the problem take a few more group iterations before it manifests itself:

  • override the Prawn::Document instance method named 'group'
  • use the code from the 'group' function from the newest development version of prawn (from github.com)

The way I did this is that I added a file to the /lib folder of my Rails app. This file will include the Prawn gems and defined the mime type for a PDF document:

class PdfPrawn
  require 'prawn'
  require 'prawn/core'
  require 'prawn/table'
  MIME_TYPE = "application/pdf"
end
class Prawn::Document
  def group(second_attempt=false)
    old_bounding_box = @bounding_box
    @bounding_box = SimpleDelegator.new(@bounding_box)

    def @bounding_box.move_past_bottom
      raise RollbackTransaction
    end

    success = transaction { yield }

    @bounding_box = old_bounding_box

    unless success
      raise Prawn::Errors::CannotGroup if second_attempt
      old_bounding_box.move_past_bottom
      group(second_attempt=true) { yield }
    end

    success
  end
end

And then in a model file, I define a method to generate my PDF and use something like this:

def to_pdf
  require "#{File.expand_path(RAILS_ROOT)}/lib/pdf_prawn"
  pdf = Prawn::Document.new
  # code to add stuff to PDF
  pdf.render
end
Teddy
  • 18,357
  • 2
  • 30
  • 42
  • Hey sorry for the late reply. Thanks for reminding me of this, I upgraded to prawn 0.11.1 and it appears you're right, the memory issues have gone. I opted to rewrite from the ground up to accommodate the new syntax and it's working beautifully now. Thanks again! – Peter Duijnstee Apr 09 '11 at 21:51