Ruport, Rails, PDFs and Column Alignment/Justification

Posted on 18 September 2008 by Johannes Fahrenkrug. Tags: Tutorials Ruby rails
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 => 20,
                  :justification => :center
          add_text data.report_title, :font_size => 18
          add_text data.report_subtitle, :font_size => 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 << 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 << col.value
              end
    
              # add the columns for the current row to the table
              t << 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 => :right}
            end
          end
    
          pad (20) { draw_table(table, :column_options => 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!


Comments

Please keep it clean, everybody. Comments with profanity will be deleted.

blog comments powered by Disqus