advertisement

Print

Ruport: Business Reporting for Ruby

by Gregory Brown and Michael Milner
04/08/2008

Many of us want to work in Ruby, with or without Rails, because it is fun. For many tasks, this is true. However, when you think of business reporting, what feelings arise? If you're like us, you might feel a bit of dread and misery. Reporting tasks tend to involve malleting data from any number of places into any number of formats, as quickly as possible. The response to all your hard work usually comes in the form of "OK, that's a decent start, but can you make this text bold and blue, and oh… we need to be able to download the report to Excel, too."

Though we won't claim that Ruby Reports (Ruport) will make your reporting tasks fun, we think it manages to make them a whole lot more tolerable. Ruport doesn't try to write your reports for you, it just provides you a solid foundation to start with. Using Ruport as a basis for your reporting applications will help you keep your code clean and organized, and keep you from going postal the next time someone asks you for a printable version of your finely crafted in-browser report.

This article is meant to give you a taste of what Ruport can do for you. We've put together the beginnings of a simple application called Bibliophile that manages book lists. By following through the examples here and playing with the source code for Bibliophile, you should be able to get a feel for what it's like to work with Ruport.

Before we dive into report generation, we will cover how to get Ruport up and running on Rails. Although it's a little more involved than a simple plugin install, it is still a very easy process.

Installation and Configuration

The first thing you need to do is install Ruport. The easiest way to do that is to install the gem.

gem install ruport

You'll also want to install Ruport's acts_as_reportable module, since it provides a connection with ActiveRecord for data collection.

gem install acts_as_reportable

That should provide everything you need to start working with Ruport. There are some other packages that provide additional functionality outside the core of Ruport, but you won't need them to get started.

Once you have everything installed, you can begin to use Ruport in your Rails projects. We're going to create a Rails project called Bibliophile that will allow us to store information about a book collection. After you create the application structure using the standard rails bibliophile command, you'll need to load Ruport in the environment.rb file to make it available. The most reliable way to do so, and be sure that Ruport is loaded at the proper time, is to add the code to require Ruport to a config.after_initialize block. The relevant section of the configuration file is shown below.

  Rails::Initializer.run do |config|
    config.after_initialize do 
      require "ruport" 
    end
  end

Now that Ruport is loaded, it will be available from within your Rails project. Note that although we also want to use the acts_as_reportable module, we don't need to specifically require it since loading Ruport will automatically attempt to load acts_as_reportable also.

When we begin to create reports, we'll need to decide on a location to put the files that will contain the reports' code. Rails, of course, establishes conventions for the directory structure and file locations for all of the files it uses, but not for the files that Ruport will need. Although it's ultimately personal preference, the Ruport community generally uses an app/reports directory to hold all reporting code. So, if you're following along, you should create that directory now, since we'll be using it later.

The final configuration step is to make the newly created app/reports directory visible to the Rails project. To do so, we add a directive to the configuration section of the environment.rb file, adding the new directory to the load path.

  config.load_paths += %W( #{RAILS_ROOT}/app/reports )

That's all there is to installing Ruport and hooking it up to a Rails project. Next, we'll examine how Ruport obtains the data that will be presented in the reports.

Data Collection Using acts_as_reportable

Before we get to generating a full report, we should take a look at data collection using Ruport. While it's possible to add free text to a report without needing any kind of data collection mechanism, in most cases you're probably going to want to present some type of tabular data as part of your finished report. Given this need, Ruport provides a set of data structures that allow you to work with tabular data easily and efficiently.

The core data structure is the Ruport::Data::Table. For the simplest case, you need to populate a Table with the data you want to present in your report. The purpose of the acts_as_reportable module is to provide an easy way to populate a Ruport Table from the data represented by an ActiveRecord Model.

For now, we're going to use the Rails console to demonstrate how to use acts_as_reportable for data collection, and we're going to use some of the built-in mechanisms provided by Ruport to output the data in a text format. Later, we'll demonstrate how to use Ruport's formatting system to customize the output.

First, we're going to need some models. Since our application is called Bibliophile, it seems apparent that we're going to need models for books and authors. The following are the migrations to create the books and authors tables.

  class CreateBooks < ActiveRecord::Migration
    def self.up
      create_table :books do |t|
        t.string :name
        t.string :description
        t.string :isbn
        t.string :status
        t.integer :author_id
        t.integer :pages
        t.integer :genre_id
      end
    end

    def self.down
      drop_table :books
    end
  end

  class CreateAuthors < ActiveRecord::Migration
    def self.up
      create_table :authors do |t|
        t.string :name
        t.timestamps
      end
    end

    def self.down
      drop_table :authors
    end
  end

In order to hook up the models to Ruport, you just add the acts_as_reportable line to each of the model definitions.

  class Book < ActiveRecord::Base
    acts_as_reportable
    belongs_to :author  
  end

  class Author < ActiveRecord::Base
    acts_as_reportable
    has_many :books
  end

This will provide each of the models with a class method called report_table that you can use to directly create a Ruport Table from the model. From the Rails console, let's create some data, so we can see how this works.

  >> Author.create(:name => 'Umberto Eco')
  >> Author.create(:name => 'William Gaddis')
  >> Author.create(:name => 'Thomas Hardy')
  >> Author.create(:name => 'Ben Okri')

Now that we have a few authors, we can see how easy it is to create a Ruport table. You just call the report_table method and the table is created for you. In order to see the structure of the resulting table, we'll use the default text output provided by Ruport.

>> puts Author.report_table
+----------------------------------------------------------------------------->>
|      name      |           updated_at           | id |           created_at >>

+----------------------------------------------------------------------------->>
| Umberto Eco    | Mon Mar 31 23:05:39 -0400 2008 |  1 | Mon Mar 31 23:05:39 ->>
| William Gaddis | Mon Mar 31 23:05:58 -0400 2008 |  2 | Mon Mar 31 23:05:58 ->>
| Thomas Hardy   | Mon Mar 31 23:06:07 -0400 2008 |  3 | Mon Mar 31 23:06:07 ->>
| Ben Okri       | Mon Mar 31 23:06:22 -0400 2008 |  4 | Mon Mar 31 23:06:22 ->>
+----------------------------------------------------------------------------->>

This is very easy, but in a real report, you might not want to show all of the columns. In fact, in the authors table, you might only care to show the authors' names. You can customize the output by supplying various options to the report_table method. You should think of this method as if it were an ActiveRecord.find method, with a few extra options used by acts_as_reportable. When you include any options, you need to specify, as the first parameter, whether you want :all or :first (:all is the default if you don't supply any parameters).

Some of the options allow you to specify which columns to include in the resulting table. The :only option specifies that only the columns specified should be included and the :except option specifies that all columns except those listed should be included. In either case, you supply the name of a column or any array of column names.

>> puts Author.report_table(:all, :only => 'name')
+----------------+
|      name      |
+----------------+
| Umberto Eco    |
| William Gaddis |
| Thomas Hardy   |
| Ben Okri       |
+----------------+

Notice also, that if you supply an array of column names to the :only option, the columns will be ordered according to their order in the array.

>> puts Author.report_table(:all, :only => ['id','name'])
+---------------------+
| id |      name      |
+---------------------+
|  1 | Umberto Eco    |
|  2 | William Gaddis |
|  3 | Thomas Hardy   |
|  4 | Ben Okri       |
+---------------------+

You can also use all of the options available in a normal ActiveRecord.find.

>> puts Author.report_table(:all, :only => ['id','name'], :order => 'authors.name')
+---------------------+
| id |      name      |
+---------------------+
|  4 | Ben Okri       |
|  3 | Thomas Hardy   |
|  1 | Umberto Eco    |
|  2 | William Gaddis |
+---------------------+

If you want to combine the output from multiple associated models, you can use the :include option. You can also nest options to the included models using hashes, allowing you to customize all of the data in the table. We'll first need to create some books, so we can see how to combine related models into one table.

>> Book.create(:name => 'Baudolino', :author_id => 1, :pages => 521)
>> Book.create(:name => 'The Famished Road', :author_id => 4, :pages => 500)

>> Book.create(:name => 'The Recognitions', :author_id => 2, :pages => 956)
>> Book.create(:name => 'The Return of the Native', :author_id => 3, :pages => 418)

The simplest use of the :include option is to just name the model to be included.

>> puts Book.report_table(:all, :only => 'name', :include => :author)
+----------------------------------------------------------------------------->>
|           name           | author.id |       author.created_at        |     >>
+----------------------------------------------------------------------------->>

| Baudolino                |         1 | Mon Mar 31 23:05:39 -0400 2008 | Mon >>
| The Famished Road        |         4 | Mon Mar 31 23:06:22 -0400 2008 | Mon >>
| The Recognitions         |         2 | Mon Mar 31 23:05:58 -0400 2008 | Mon >>
| The Return of the Native |         3 | Mon Mar 31 23:06:07 -0400 2008 | Mon >>
+----------------------------------------------------------------------------->>

In order to fully customize the output, you may need to supply options to the included models as well. Note that the names of the columns returned from the included models are qualified with the name of the model.

>> puts Book.report_table(:all, :only => 'name', :include => { :author => { :only => 'name' } })
+-------------------------------------------+
|           name           |  author.name   |
+-------------------------------------------+
| Baudolino                | Umberto Eco    |
| The Famished Road        | Ben Okri       |
| The Recognitions         | William Gaddis |
| The Return of the Native | Thomas Hardy   |
+-------------------------------------------+

You should now have a basic idea of how to create a Ruport table from an ActiveRecord model (or multiple models). There are some other advanced instructions that acts_as_reportable understands, which you can explore on your own. For now, we'll move on to show you how to use the data you've just collected to create a formatted report.

Pages: 1, 2, 3

Next Pagearrow