advertisement

Print

Ruport: Business Reporting for Ruby
Pages: 1, 2, 3

Report Formatting

Ruport takes a very organized approach to formatting your reports. Rather than mixing your data manipulation code with your format specific code, we maintain a clean separation that is quite similar to the MVC pattern in Rails.



Like Rails, Ruport has controllers that act as the go-between for your data and the code that will ultimately format it. The best way to see how this works is by example, so let's build upon what we've gone over in the last section and produce a simple Book list. We'll add other formats later, but for now, we can start with HTML.

The following code implements a simple report that displays the title, author, and number of pages for all the books in the Bibliophile application.

app/reports/book_report.rb

class BookReport < Ruport::Controller

  stage :list

  def setup
    self.data = Book.report_table(:all, :include => { :author => { :only => ["name"] } },
                                         :only => ["name", "author.name", "pages"],
                                         :order => "books.name")
    data.rename_columns("name" => "Title", "author.name" => "Author")
  end

  formatter :html do
    build :list do
      output << textile("h3. Book List")
      output << data.to_html
    end
  end

end

Using script/console, we can actually run this report:

>> puts BookReport.render_html
<h3>Book List</h3>      <table>
                <tr>

                        <th>Title</th>
                        <th>Author</th>
                        <th>pages</th>
                </tr>

                <tr>
                        <td>Baudolino</td>
                        <td>Umberto Eco</td>
                        <td>521</td>

                </tr>
                <tr>
                        <td>The Famished Road</td>
                        <td>Ben Okri</td>
                        <td>500</td>

                </tr>
                <tr>
                        <td>The Recognitions</td>
                        <td>William Gaddis</td>
                        <td>956</td>

                </tr>
                <tr>
                        <td>The Return of the Native</td>
                        <td>Thomas Hardy</td>
                        <td>418</td>

                </tr>
        </table>

The resulting HTML is quite vanilla, and shouldn't come as a surprise to anyone. A little later on, we'll show you how to wire up your Rails controllers and views to display this report, but for now, let's take a closer look at what Ruport is doing here.

Ruport controllers work by processing reports in stages, which are defined by your formatters. In this very simple report, there is only a single stage our formatters can build, the list. The code that defines this is shown below:

  stage :list

With this in mind, the formatter code is probably a little clearer:

  formatter :html do
    build :list do
      output << textile("h3. Book List")
      output << data.to_html
    end
  end

It's clear that the HTML formatter gets the code inside the build block executed when BookReport.render_html, but it might be a little tougher to figure out where things like the output and textile methods are coming from.

To simplify things a bit, we can unravel Ruport's syntactic sugar and take a look at how this comes together in plain old Ruby objects. This report could easily be re-written like this:

class BookReport < Ruport::Controller

  stage :list

  def setup
    self.data = Book.report_table(:all, :include => { :author => { :only => ["name"] } },
                                         :only => ["name", "author.name", "pages"],
                                         :order => "books.name")
    data.rename_columns("name" => "Title", "author.name" => "Author")
  end

  class HTML < Ruport::Formatter::HTML

    renders :html, :for => BookReport

    def build_list
      output << textile("h3. Book List")
      output << data.to_html
    end
  end

end

When we write it this way, it becomes clear that Formatter objects in Ruport are actually separate entities from the controllers, sharing only the details they need to. Specifically, the only two bits of data shared between a Controller and a Formatter are the data and options attributes.

The code that allows this to happen is shown below:

  renders :html, :for => BookReport

This line tells the BookReport controller that our subclass of Ruport's HTML formatter will handle the HTML output for that controller. This tells the controller that when BookReport.render_html is called, this object will be the one it executes its stages on.

In this code, we find that the build method we used before with a block is nothing more than syntactic sugar that produces vanilla Ruby methods.

  def build_list
    output << textile("h3. Book List")
    output << data.to_html
  end

At the bottom of the chain here, we find that there is little magic to be found and we can come up with a nice summary of how Ruport's formatting system works in the context of this report.

When BookReport.render_html is called, the following steps are taken, in order:

  1. BookReport looks for the Formatter registered as :html.
  2. The setup method is run, allowing data and options to be tweaked as needed.
  3. Stages are run in the order they are defined.
  4. The output of the formatter is returned.

Though this process is a bit more complex for advanced uses of the formatting system, these steps form the core of what happens when you render a report in Ruport.

Now that we've covered how the system actually works, let's take a quick look at adding CSV and PDF support to this report.

  formatter :pdf do
    build :list do
      pad(10) { add_text "Book List" }
      draw_table data
    end
  end

  formatter :csv do
    build :list do
      output << data.to_csv
    end
  end

The CSV format is generic as they come, as you can see, Ruport's tables have built in formatting support, and for the CSV, we just want a simple dump of the data.

Taking a look via script/console, you'll see that's exactly what we get.

>> puts BookReport.render_csv 
Title,Author,pages
Baudolino,Umberto Eco,521
The Famished Road,Ben Okri,500
The Recognitions,William Gaddis,956
The Return of the Native,Thomas Hardy,418

Our PDF output is only slightly more interesting. The first line adds some text to the document with a padded margin at the top and bottom:

  pad(10) { add_text "Book List" }

The second line may be a bit surprising, because you may have expected something like output << data.to_pdf. However, PDF is not a streaming data format like HTML, text, or CSV, so we need to use a special helper to draw the table on the PDF canvas.

  draw_table data

Other than that, the code is very basic. Ruport's controllers support rendering to a file, so we can do that to get our PDF back:

>> BookReport.render_pdf(:file => "books.pdf")

The output looks something like this:

figure

The output looks fairly generic, and it'd probably be nicer to have the table heading centered over the table and in a larger font. Though this is possible to do with the low level PDF formatter helpers, we'll instead look at a higher level system in Ruport known as formatter templates.

Pages: 1, 2, 3

Next Pagearrow