⚠️ Archived Post
This is an archived post from my previous blog (2007-2014). It may contain outdated information, broken links, or deprecated technical content. For current writing, please see the main Writing section.

Ruport, Rails, PDFs and Column Alignment/Justification

Originally published on September 18, 2008

I’m currently working on a Rails project that needs to generate Report PDFs. Ruport, of course, is an obvious choice for this task. It took me a little bit to get used to how Ruport does things (read: wants you to do things). I don’t want to dive too deep into getting started with Ruport. If you’re new to it, check out these two great guides: The Dimwit’s Guide and O’Reilly Network Ruport Guide. Please note, however, that the tutorials and guides often mention “Renderers”: they are called “Controllers” in the current version of Ruport. Just keep that in mind. So back to my PDF columns problem: I wanted to be able to have all the text in the first column left-aligned and the text in all the other columns right-aligned. This turned out to be a little harder than I thought. Let’s do this step by step (I assume you have installed the Ruport Gem and it’s dependencies). This will also show you how to use Ruport in your Rails app without acts_as_reportable:

  1. Create a "reports" directory inside your app directory.
  2. Add it to your Rails load path by adding this inside the Rails::Initializer.run block in your environment.rb:
    config.load_paths += %W( #{RAILS_ROOT}/app/reports )
  3. For the next step, we assume to have class that looks something like this:
    class MyReport < ActiveRecord::Base
      validates_presence_of :report_title, :report_subtitle
    

    ColumnHeader has an attribute “name”

    has_many :column_headers

    We assume they come in the right order, and a row has_many columns.

    A Column has an attribute “value”

    has_many :rows end

    So we see our report can have n rows with n columns and two attributes: "report_title" and "report_subtitle".
  4. Create a file called "my_report_renderer.rb" in your app/reports directory.
  5. Put this in the file:
    require 'ruport'
    

    class MyReportRenderer < Ruport::Controller stage :report_header, :report_body finalize :report

    class PDF < Ruport::Formatter::PDF renders :pdf, :for => MyReportRenderer

    def build_report_header
      # data is an instance of your MyReport class
      add_text "My Great Report", :font_size =&gt; 20,
              :justification =&gt; :center
      add_text data.report_title, :font_size =&gt; 18
      add_text data.report_subtitle, :font_size =&gt; 16
    end
    
    def build_report_body
      # we have to build the table
      headers = []
    
    
      # gather the column header strings in an array
      data.column_headers.each do |ch|
        headers &lt;&lt; ch.name
      end
    
    
      table = Table(headers) do |t|
        data.rows.each do |row|
          columns = []
    
          # gather the actual column values for each row in an array
          row.columns.each do |col|
            columns &lt;&lt; col.value
          end
    
          # add the columns for the current row to the table
          t &lt;&lt; columns
        end
      end
    
      # create column option that right justify all columns but the first one
      col_opts = {}
      header_index = 0
      for header_index in (0..(headers.length - 1))
        # we'll skip the first one, since :left is the default
        if header_index != 0
          # the options for each column are indexed by it's name (headers array)
          col_opts[headers[header_index]] = {:justification =&gt; :right}
        end
      end
    
      pad (20) { draw_table(table, :column_options =&gt; col_opts) }
    end
    
    def finalize_report
      render_pdf
    end

    end end

    OK, now we have a renderer (or controller) that renders our report with all columns right-justified except for the first one.
  6. Now just add an action to your MyReport Rails controller:
    def download_pdf
    begin
    @report = MyReport.find(params[:id])
    send_data(MyReportRenderer.render(:pdf, :data => @report),
    :filename => “report#{params[:id]}.pdf”,
    :type => “application/pdf”,
    :disposition => ‘inline’)
    rescue ActiveRecord::RecordNotFound
    flash[:error] = “Report not found.”
    redirect_to :action => :index
    end
    end
  7. That’s it.

I didn't actually try this code: it's modified (simplified) code from the app I'm working on, but it should work. If not, please leave a comment.

If this was useful for you, please take a minute and recommend me: Recommend Me Thank you!