Ruport: Business Reporting for Ruby
Pages: 1, 2, 3
Formatter Templates
Templates are meant to provide some abstraction by separating default formatting values from the individual formatters. Though formatters in Ruport ultimately can choose what, if any, template options to implement, the general goal of templates it to provide a normalized interface to your formatting options.
Templates are simple to create, so we'll take a quick look at how to use them to manipulate the current PDF report. The following template centers and increases the size of text drawn by add_text.
app/reports/templates.rb
Ruport::Formatter::Template.create(:default) do |format|
format.text = {
:font_size => 16,
:justification => :center
}
end
To get this to work, we'll need to add an explicit require in our environment.rb, since it won't be possible to use class lookups to autoload the file.
require "app/reports/templates"
Once this is set, without any changes to the underlying code, you can regenerate a PDF that looks like this:

It's worth noting that this template will be used by all your controllers by default. If you find that's not what you want, you can give your template a name, and then refer to it by name when rendering your output.
# in your templates file Ruport::Formatter::Template.create(:book_list) do |format| # ... end # when you render output BookReport.render_pdf(:template => :book_list)
You can even derive a template from another template:
Ruport::Formatter::Template.create(:small_centered, :base => :default) do |format| format.text.merge!(:font_size => 10) end
Finally, when working with templates, if you want to be sure that your controller ignores all templates, you can do so:
BookReport.render_pdf(:template => false)
Ruport's formatters support a wide range of templating options, which you can find by browsing the API documentation.
This gives you just a taste of templates, but they're worth knowing about. You can build your own custom formatters in Ruby, and by implementing hooks that understand some templating options, you can make things quite flexible.
We'll stick to the basics for now though, so let's move on to getting this report working in your Rails application.
Wiring Up Your Rails Controllers and Views
Now that we have a basic report, we can see how you might generate it from a Rails controller. Perhaps the easiest of the formats to integrate with your Rails project is HTML. Since the report will produce HTML output, you can just insert it into one of the views wherever you want it.
Let's create an index action in our Rails controller and just generate the HTML report, saving it to an instance variable.
class BooksController < ApplicationController
def index
@book_report = BookReport.render_html
end
end
Then, the corresponding view can be as simple as:
<%= @book_report %>
Our view in Bibliophile is only slightly more complicated, mostly due to styling. You can see that it centers around the same basic report though:

Generating the other formats isn't much harder, but you need to consider how you'll return the requested data. You won't be able to just render it directly, so you need to use the Rails send_data method to stream the results to the browser. The report itself, however, is generated in the same way, just substituting the appropriate format.
Let's take a look at how you might generate printable PDF output. We can add another method to the controller in order to create the PDF.
class BooksController < ApplicationController
def printable_list
pdf = BookReport.render_pdf
send_data pdf, :type => "application/pdf",
:filename => "books.pdf"
end
end
You can see that we render the PDF report in the same manner as we did the HTML report. However, in this case, we save the results to a variable and then supply those results as the data for the send_data method. We also specify the content type and filename. Other than that, you just need to add a link to this method to be able to generate a PDF version of the report.
Just to be thorough, here is the controller action to generate the CSV output from the report. It follows the same pattern as the others.
class BooksController < ApplicationController
def csv_list
csv = BookReport.render_csv
send_data csv, :type => "text/csv",
:filename => "books.csv"
end
end
Integrating report generation into Rails controllers is as simple as that and in many cases, that will be all you need.
Filtering Report Data
If you looked closely at the screenshot of Bibliophile, you might have noticed that the report could be filtered by author names. This kind of functionality is an extremely common need with Rails based reporting, ranging from something as simple as filtering based on a single field to building full blown query generators to narrow down your reports.
Though this particular example represents the most simple case, the general pattern can be built upon to implement arbitrarily complex filtering systems.
The feature requires changes to both the Rails controller and the BookReport. Let's take a look at the Ruport code first, since it's the interesting part:
class BookReport < Ruport::Controller
stage :list
def setup
conditions = ["authors.id = ?", options.author] unless options.author.blank?
self.data = Book.report_table(:all, :include => { :author => { :only => ["name"] } },
:only => ["name", "author.name", "pages"],
:order => "books.name",
:conditions => conditions)
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
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
end
Looking at the modifications to the Ruport, you can see very little has changed. Since the formatters all use the data provided by the BookReport controller, they have not changed. The only new code is in setup(), which is simply creating some conditions that will be passed back to the underlying ActiveRecord#find call. Perhaps the only surprising thing is that the report is now referencing an options.author attribute.
A quick dance with script/console should shed light on this:
>> Author.find(1).name => "Umberto Eco" >> puts BookReport.render_csv(:author => 1) Title,Author,pages Baudolino,Umberto Eco,521
As you can see from the example above, Ruport takes any option passed in at rendering time and assigns them to an options object. The only exceptions are the few special keywords, such as :file, :data, and :template.
This turns out to be extremely useful, because it allows arbitrary options to be passed to your controllers and formatters. In case you were curious, it's worth noting that these values can also be accessed in a hash like manner, such as options[:author].
We already have a working filtering mechanism, so all that remains is to get it working within the context of Rails:
class BooksController < ApplicationController
def index
session[:author] = params[:author]
@book_report = render_book_list_as :html
@authors = Author.find(:all)
end
def printable_list
pdf = render_book_list_as :pdf
send_data pdf, :type => "application/pdf",
:filename => "books.pdf"
end
def csv_list
csv = render_book_list_as :csv
send_data csv, :type => "text/csv",
:filename => "books.csv"
end
protected
def render_book_list_as(format)
BookReport.render(format, :author => session[:author])
end
end
As you can see, the change here is nothing fancy. The main index page which shows the HTML report persists the selected author in the dropdown menu in the session. This value is then passed on when any of the HTML, CSV, or PDF formats are rendered. We have created a simple helper method to avoid needless duplication, but the code is otherwise the same as before.
Filtering obviously can get more complex than this. We won't cover it here, as it tends to be more Rails code than Ruport, but it's worth mentioning that acts_as_reportable supports some additional options that might be useful for implementing data filters. If you're working on this kind of task, be sure to look at the documentation report_table's :filters and :transforms options.
Just the Tip of the Iceberg
In the interest of keeping things simple and easy to approach, we've not gone into many of Ruport's advanced features in this article. We have definitely shown all of the major components of the system, but have glossed over most of the advanced features, especially those that are a little bit specialized in purpose.
We hope that the simple examples here have offered a taste of what Ruport can do for you, and given you a starting point for continuing to explore its possibilities. If you found this interesting, definitely consider browsing Ruport's API documentation or taking a look through the Ruport Book, which has a free HTML version available for online browsing.
Finally, don't hesitate to get involved! Our mailing list is one of the best resources for learning the software, and we welcome users to come join our community and help us make Ruport better.
Please enjoy working with Ruport, and Happy Hacking!
Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.
Michael Milner is an active member of the Ruby community. He is currently the lead developer for the Ruport project and also maintains the Ruport/Rails plugin that provides Rails integration for Ruport. He works professionally using Ruby and has developed large web applications using Rails
Return to O'Reilly Ruby.



