Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save roooodcastro/05efe5bde441f408b88d to your computer and use it in GitHub Desktop.
Save roooodcastro/05efe5bde441f408b88d to your computer and use it in GitHub Desktop.
Rails Prawn: How to print different data per page inside a dynamic repeater

The problem with repeaters

How does Prawn's repeaters work?

Prawn is an amazingly configurable and flexible tool for PDF generation. It allows you to create boring financial style PDFs that can almost mimic a (static) Excel spreadsheet, and can also create a convincing party invitation letter.

One of Prawn's many tools to allow this kind of flexibility is the Repeater. It allows you to define a section of your PDF, and reuse it over and over, essentially stamping it on every page (or just on odd pages, or on the first 10 pages, etc, you can also configure that).

Let's create a simple PDF document as an example:

app/pdfs/base_pdf.rb:

class BasePdf < Prawn::Document

def initialize
  super()
  generate_pdf
end

def generate_pdf
  repeat :all do
    text 'Test'
  end

  3.times { start_new_page }
end
end

app/controllers/application_controller.rb:

def report
  send_data BasePdf.new.render, type: 'application/pdf', disposition: 'inline'
end

Now we need to go to localhost:3000/report.pdf (after configuring the route) and the PDF will be displayed, which will contain 3 pages with the same 'Test' text printed on each of them.

Let's now understand how this happens: When that code calls repeat :all, and pass a block to it, it's essentially telling Prawn to:

  1. Create a new Stamp (think of it as an actual stamp).
  2. Repeat this stamp on all pages of the PDF (3 in our example). It also doesn't matter if you called repeat after calling start_new_page before, it will render the repeater on every page.
  3. Render whatever is inside the block into this new stamp (in our case a simple text), so when Prawn "stamps" it it will stamp exactly the same thing over and over.

However, this "stamping" doesn't happen immediately, nor does it happen when a new page is started. Prawn will only "stamp" the repeater after the whole document has been defined, when the method render is called. Usually you want to call render when you want to send the PDF to the user, so it will always be at the end.

The issue with dynamic repeaters

What if you want to change what's inside this Stamp? Let's image a new situation, more akin to real world, where I have a PDF with many pages, and I need to print different information on the header for different pages. If you already know the exact number of pages the PDF will have and which page have which information, you can do something like this:

app/pdfs/base_pdf.rb

def header
  repeat :all, dynamic: true do
    if (page_number < 5)
      text 'One thing'
    else
      text 'Other thing'
    end
  end
end

This is all fine and dandy, until you get yourself into a situation like this:

app/pdfs/base_pdf.rb

  # I want to print the name of the country of the first city in the current page
  def header_country
    repeat :all, dynamic: true do
      text @current_country
    end
  end

  # We'll assume cities is a hash, where each key is a country and contains an array of cities for each country
  def print_cities_of_the_world(all_cities)
    all_cities.each do |country, cities|
      @current_country = country
      cities.each do |city|
        text city
      end
    end
  end

That's why dynamic repeaters exist. They allow you to change the contents of the stamp for each page.

Changing repeater content based on current page

A better solution

@justinxreese
Copy link

What is the better solution? Struggling with this exact problem and that was the worst cliffhanger in my life

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment