A common need in reporting is to be able to display the same data in a number of different formats. Ruport’s Formatting System provides a highly flexible way to separate your rendering process from your specific formatting code. This cheatsheet shows how the parts come together and the tools you have available to you.
Building a custom renderer allows you to define the steps which should be taken to produce your reports, as well as the options that will be available to them.
The following simple example shows a fully functional renderer:
class InvoiceRenderer < Ruport::Renderer
stage :company_header, :invoice_header, :invoice_body, :invoice_footer
required_option :employee_name, :employee_id
option :note
end
Without any more code, the above defines what our interface will look like. We know that in order to render a given format, we’ll be writing something like this:
InvoiceRenderer.render(:some_format) do |r|
r.data = my_data
r.employee_name = "Samuel L. Jackson"
r.employee_id = "e1337"
end
Alternatively, we could use some shortcuts:
InvoiceRenderer.render_some_format(:data => my_data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337")
The power here of course is that even as new formats are added, our external interface stays the same. We’ll now show how formatters are implemented, to give you an idea of how stages work.
A Formatter implements one or more labeled formats which are registered on one or more Renderer objects. Here we show a simple text formatter for the InvoiceRenderer.
class Text < Ruport::Formatter
renders :text, :for => InvoiceRenderer
def build_company_header
output << "My Corp. Standard Report\n\n"
end
def build_invoice_header
output << "Invoice for #{options.employee_name} " <<
"(#{options.employee_id}), generated #{Date.today}\n\n"
end
def build_invoice_body
data.each do |r|
output << "#{r[:service].ljust(40)} | #{r[:rate].rjust(10)}\n"
end
end
def build_invoice_footer
output << "\n#{options.note}\n\n" if options.note
end
end
Looking back at the renderer, you can see that stage :some_stage gets translated to build_some_stage in the formatter. Although the renderer will attempt to call all of these hooks, it will happily pass over any that are missing. This means that if you did not want to include the company header in a given format, you could just leave build_company_header out of your formatter.
You’ll also notice that the options passed into the Renderer can be accessed via the options collection in the formatter.
You can also see that the formatter registers itself with the InvoiceRenderer, via:
renders :text, :for => InvoiceRenderer
The following chunk of code shows our Renderer/Formatter pair in action.
data = Table(:service,:rate)
data << ["Mow The Lawn","50.00"]
data << ["Sew Curtains", "120.00"]
data << ["Fly To Mars", "10000.00"]
puts InvoiceRenderer.render_text(:data => data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337",
:note => "END OF INVOICE")
OUTPUT:
My Corp. Standard Report
Invoice for Samuel L. Jackson (e1337), generated 2007-08-01
Mow The Lawn | 50.00
Sew Curtains | 120.00
Fly To Mars | 10000.00
END OF INVOICE
The following definition adds XML format to our report.
class XML < Ruport::Formatter
renders :xml, :for => InvoiceRenderer
opt_reader :employee_name, :employee_id, :note
def layout
output << "<invoice>\n"
yield
output << "</invoice>"
end
def build_invoice_body
add_employee_info
generate_data_rows
add_meta_data
end
def add_employee_info
output << "<employee name='#{employee_name}' id='#{employee_id}'/>\n"
end
def generate_data_rows
data.each do |r|
output << "<item charge='#{r[:rate]}'>#{r[:service]}</item>\n"
end
end
def add_meta_data
output << "<note>#{note}</note>\n<created>#{Date.today}</created>\n"
end
end
There are a few things which make this different from our text formatter, but at the heart it is the same general idea.
We’ve used opt_reader to save us some typing. You’ll notice that by doing this we can refer to the options directly without the prefix.
You’ll also notice that we use a layout for this code. This allows us to have some additional control over the rendering process, allowing us to run some code before and after the stages are executed. In this case, we’re simply wrapping the output in an <invoice> tag.
We’ve also only implemented one of the many stages the renderer tries to call. This is because we’re generating output for serialization, so we have no need for headers and footers.
To generate XML instead of Text, you’ll notice its only a couple characters that need changing:
puts InvoiceRenderer.render_xml( :data => data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337",
:note => "END OF INVOICE")
OUTPUT:
<invoice>
<employee name='Samuel L. Jackson' id='e1337'/>
<item charge='50.00'>Mow The Lawn</item>
<item charge='120.00'>Sew Curtains</item>
<item charge='10000.00'>Fly To Mars</item>
<note>END OF INVOICE</note>
<created>2007-08-01</created>
</invoice>
Using the techniques from above, you can easily build an extension to our standard renderers.
The following example adds XML support for our Table renderer:
class XML < Ruport::Formatter
renders :xml, :for => Ruport::Renderer::Table
def layout
output << "<table>\n"
yield
output << "</table>\n"
end
def build_table_header
output << "<header>\n"
output << build_row(data.column_names)
output << "</header>\n"
end
def build_table_body
output << "<body>\n"
data.each { |r| output << build_row(r) }
output << "</body>"
end
def build_row(row)
"<row>\n <cell>" <<
row.to_a.join("</cell><cell>") <<
"</cell>\n</row>\n"
end
end
This makes the formatter immediately available for use with Ruport’s Data::Table structure.
data = Table(:service,:rate)
data << ["Mow The Lawn","50.00"]
data << ["Sew Curtains", "120.00"]
data << ["Fly To Mars", "10000.00"]
puts data.to_xml
OUTPUT:
<table>
<header>
<row>
<cell>service</cell><cell>rate</cell>
</row>
</header>
<body>
<row>
<cell>Mow The Lawn</cell><cell>50.00</cell>
</row>
<row>
<cell>Sew Curtains</cell><cell>120.00</cell>
</row>
<row>
<cell>Fly To Mars</cell><cell>10000.00</cell>
</row>
</body>
</table>
Of course, you can and should use your favorite Ruby XML builder for any task more interesting than this. Ruport happily wraps any third party library you’d like to use.
Templates allow you to define a reusable set of formatting options. You can create multiple templates with different options and specify which one should be used at the time of rendering.
Define a template using the create method of Ruport::Formatter::Template.
Ruport::Formatter::Template.create(:simple) do |t|
t.note = "END OF INVOICE"
end
Ruport::Formatter::Template.create(:other) do |t|
t.note = "END"
end
Then define an apply_template method in your formatter to tell it how to process the template.
class Ruport::Formatter::Text
def apply_template
options.note = template.note
end
end
To use a particular template, specify it using the :template option when you render your output.
data = Table(:service,:rate)
data << ["Mow The Lawn","50.00"]
data << ["Sew Curtains", "120.00"]
data << ["Fly To Mars", "10000.00"]
puts InvoiceRenderer.render_text(:data => data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337",
:template => :simple)
puts InvoiceRenderer.render_text(:data => data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337",
:template => :other)
puts InvoiceRenderer.render_text(:data => data,
:employee_name => "Samuel L. Jackson",
:employee_id => "e1337")
To derive one template from another, use the :base option to Ruport::Formatter::Template.create.
Ruport::Formatter::Template.create(:derived, :base => :simple)
Ruport has a standard template interface to each of the built-in formatters. This example shows a number of the options being used.
Ruport::Formatter::Template.create(:simple) do |t|
t.page_format = {
:size => "LETTER",
:layout => :landscape
}
t.text_format = {
:font_size => 16
}
t.table_format = {
:font_size => 16,
:show_headings => false
}
t.column_format = {
:alignment => :center,
:heading => { :justification => :right }
}
t.grouping_format = {
:style => :separated
}
end
What was shown here are simply the formatting system basics. You can actually do a whole lot more as your tasks become more complicated.
If you’re looking for an easy way to normalize your data before it is formatted, or share logic between formatters, see the Custom Renderer Logic cheatsheet.
If you’re looking to build printable reports in PDF format and would like to take advantage of some of Ruport’s helpers, see the Printable Documents cheatsheet.
There are also some helpful examples in “Integration Hacks” that show how to quickly wrap existing code with Ruport’s formatting system.
The API docs for the renderers all list the hooks they implement and the options they receive. This will be helpful if you are looking to extend our built-in renderers.
The API docs for Ruport::Formatter::Template list all of the options/values available in the template interface to the built-in formatters.
Adding new formats to built in renderers
Related Resources / Digging Deeper
Please report them on the Ruport Mailing List
Please support the Ruby Reports Documentation Effort