Quando si ha a che fare con una banca dati, a prescindere dalla natura delle informazioni, che essa sia rappresentata da un database o da un grosso file, ci si deve confrontare con la necessità di confezionare delle visualizzazioni, dei report.
Questo per avere risultati utili e semplici da analizzare e manipolare, con tabelle, immagini e grafici, soprattutto se prodotti nei formati più usati come PDF o Excel (ma anche Word, CSV etc.).
In questo articolo ci occupiamo della creazione di report con Ruport, dividendo il discorso in due parti. Nella prima parleremo dei concetti di base della libreria:
- strutture dati
- manipolazione dati
- controller
- formatter
column_names
remove_column("album")
remove_columns("album", "year")
rename_column('old_name', 'new_name')
rename_columns ('old_names', 'new_names')
swap_column('a', 'b')
replace_column('old_col', 'new_col')
add_column('c', :before => 'd')
sub_table(%w[album year])
- I Group
Ruport::Data::Group
Ruport::Data::Table
name
- I Grouping
Ruport::Data::Grouping.new
Grouping
- Il Controller
- Il Formatter
- integrare i report nelle nostra applicazioni Rails
- creare dei grafici
- personalizzare i template
- esportare i report in formati OpenDocument
- ruport-util
- documatic OpenOffice
- Per la generazione di output in formato PDF, Ruport fa uso di pdf-writer
PDF::Writer
pdf_writer_proxy
:only
:except
:methods
:include
:filters
:transforms
- Utilities
build
sql_exec
Rakefile
- Directories
test
config
reports
controllers
models
sql
util
Nella seconda, invece, estenderemo alcuni concetti e approfondiremo temi quali la gestione dei template per la formattazione, la generazione di grafici, le estensioni per la generazione di file Excel e l'integrazione con Ruby on Rails.
Inoltre ci serviremo di esempi che aiuteranno a capire ed apprezzare funzionalità e potenzialità di Ruport.
Ruport: cosa è e cosa offre?
Ruport arrivato alla versione 1.6.1, sviluppato da Gregory Brown, Dudley Flanders, James Healy, Dinko Mehinovic e Michael Milner, fornisce un insieme di tool che aiutano lo sviluppatore ad aggiungere capacità reportistiche alle proprie applicazioni.
Offre una serie di facilitazioni per l'estrazione, il raggruppamento e la manipolazione di dati, oltre alla capacità di renderli disponibili in diversi formati di output grazie ad un sistema flessibile ed estensibile per la formattazione ed il rendering dei report.
Prepariamo il campo di lavoro
Prima di iniziare ad analizzare le potenzialità di Ruport, provvediamo all'installazione tramite l'ormai noto RubyGems
.
gem install ruport
che provvederà ad installare automaticamente le dipendenze (fastercsv,
).
pdf-writer
Le strutture dati di Ruport
Ruport usa quattro strutture dati di base: record
, table
, group
e grouping
, tutte contenute nel modulo Ruport::Data
. Il nucleo delle strutture dati è la tabella. I record formano le tabelle, i group
ed i grouping
servono per raggruppare i dati in tabella.
Iniziamo a crearne qualcuna. Partiamo dai record
.
I Record rappresentano la struttura dati più semplice. Corrispondono ad una riga di dati proveniente da database o da altre sorgenti di dati.
Listato 1. Creare record
%w[rubygems ruport].each {|l| require l} column_headers = ["artist", "album", "year", "tracks"] first_item = Ruport::Data::Record.new(["King Crimson", "In the Court of Crimson King", "1969", "5"], :attributes => column_headers) => #<Ruport::Data::Record:0x2d1b864 @data={"artist"=>"King Crimson", "year"=>"1969", "tracks"=>"5", "album"=>"In the Court of Crimson King"}, @attributes=["artist", "album", "year", "tracks"]> second_item = Ruport::Data::Record.new(["King Crimson", "In the Court of Crimson King", "1969", "5"], :attributes => column_headers) third_item = Ruport::Data::Record.new(["Genesis", "Nursey Crime", "1972", "7"], :attributes => column_headers) fourth_item = Ruport::Data::Record.new(["Frank Zappa","We're Only In It For The Money", "1968", "19"], :attributes => column_headers)
L'accesso ai dati del record può avvenire usando una notazione array-like o hash-like, rispettivamente first_item[1]
e first_item["album"]
.
Le Tabelle sono collezioni di Record
e presentano i metodi necessari per lavorare con i dati in esse conte
Listato 2. Creare e visualizzare una tabella
table = Ruport::Data::Table.new => #<Ruport::Data::Table:0x2d2fb48 @record_class="Ruport::Data::Record", @data=[], @column_names=[]> table.column_names = column_headers table << first_item << second_item << third_item << fourth_item puts table +---------------------------------------------------------------+ |artist | album | year | tracks | +---------------------------------------------------------------+ | King Crimson | In the Court of Crimson King | 1969 | 5 | | King Crimson | In the Court of Crimson King | 1969 | 5 | | Genesis | Nursey Crime | 1972 | 7 | | Frank Zappa | We're Only In It For The Money | 1968 | 19 | +---------------------------------------------------------------+
Come accennato precedentemente, i metodi disponibili sulla struttura dati Table sono molti. Vediamone alcuni:
Per un elenco completo fare riferimento alla documentazione ufficiale.
I dati possono essere raggruppati secondo criteri a nostra scelta. Ruport fornisce due strutture dati per questo:
Listato 3. Creazione di un Grouping
artist_grouping = Grouping(table, :by => "artist") year_grouping = Ruport::Data::Grouping.new(table, :by => ["artist", "year"]) puts artist_grouping puts year_grouping
Manipolazione dei dati
Con poche linee di codice, spesso ne basta solo una, è possibile eseguire manipolazioni dei dati presenti nelle strutture prima descritte. Questa funzionalità, insieme al fatto che i dati possono provenire da diverse sorgenti ed essere nativamente pronti per la visualizzazione in vari formati di output, rendono Ruport adatto alle esigenze di ogni sviluppatore.
Prendiamo ora in considerazione operazioni quali l'ordinamento, la ricerca, le operazioni su colonna, il calcolo della somma e della media dei dati in colonna.
Ordinare le tabelle
puts table.sort_rows_by("year", :order => :ascending)
Ordinare i Grouping
Nel più semplice dei casi possiamo ordinare un Grouping dal nome del gruppo impostando l'opzione :order
al valore del :name
del gruppo.
g = Grouping(table, :by => "tracks", :order => :name) puts g.to_a.map {|name,group| name}
oppure ordinandolo in base ad uno specifico blocco:
g = Grouping(table, :by => "artist", :order => lambda {|g| g.size }) puts g.to_a.map {|name,group| name }
o ancora, grazie al metodo sort_grouping_by
dopo che è stato creato:
g = Grouping(table, :by => "year") puts g.sort_grouping_by {|g| g.size }
Cercare righe in una tabella
Ruport mette a disposizione del tipo Table
il metodo rows_with
, perfetto per le semplici operazioni di ricerca:
table.rows_with("artist") { |a| if a.size < 11 puts a end }
Se ad esempio volessimo conoscere il numero totale di tracce presenti nella nostra collezione di Cd e la media di tracce per cd, potremmo usare il metodo Table#sigma
o il suo alias Table#sum
# Totale # Media
Filtrare e trasformare i dati
Ruport::Data::Feeder
fornisce un semplice proxy object che ci permette di filtrare i dati che vogliamo aggregare. È principalmente usato per creare una tabella wrapper con vincoli. Può essere usato anche con strutture dati astratte.
# Esempio tratto dalla documentazione ufficiale # http://api.rubyreports.org/classes/Ruport/Data/Feeder.html
Creazione dell'output
Tutte le strutture dati di base di Ruport possono produrre nativamente output in diversi formati (PDF, Text, HTML, CSV). Per farlo basta semplicemente richiamare il metodo corrispondente:
Listato 4. Formati di output per i tipi dati nativi di Ruport
# TXT # CSV # HTML # PDF
Per situazioni semplici o per i test, le capacità di output native delle strutture dati rappresentano davvero un aiuto.
Il sistema di formattazione di Ruport ci consente di personalizzare la formattazione ed il rendering dei nostri dati. È costituito da due componenti:
In Ruport gli step definiti nel Controller prendono il nome di stage. Definendo gli stage, il Controller
sa che dovrà fare riferimento alle rispettive implementazioni presenti nel Formatter
a lui associato al fine di produrre l'output.
Per la creazione dei metodi del Formatter
corrispondenti agli stage
del Controller, Ruport segue una convenzione interna. Fa uso del metodo di classe build
con il nome dello stage
e gli associa un block (il corpo del metodo). Nel caso non si volesse seguire la convenzione, nulla ci vieta di definire il metodo build_nomestage
.
Esempio finale
In questo esempio mettiamo insieme i concetti finora esposti al fine di fornire un esempio di gestione e manipolazione dati, nonché di creazione di controller e formatter per i nostri report.
Listato 5. Esempio finale
# Definiamo il Controller # Formatter per l'output testuale # Formatter per l'output HTML # con template ERB
Abbiamo accennato ai concetti ed alle funzionalità di base di Ruport. Nella seconda parte dell'articolo descriveremo le funzionalità permettono di creare grafici (SVG o su PDF), personalizzare il layout e interagire con RubyonRails.
Nella prima parte dell'articolo ci siamo occupati degli aspetti di base della libreria, di come si creano e visualizzano tabelle, delle manipolazioni semplici dei dati e di come creare output personalizzato tramite la definizione di controller e formatter. Ora discuteremo di come:
ed infine accenneremo all'uso di rope.
Aggiungiamo il necessario al campo di lavoro
Con RubyGems
provvediamo all'installazione delle gem necessarie (acts_as_reportable
, ruport_util
, documatic
, pdf_writer_proxy
).
gem install ruport-util documatic acts_as_reportable gem install pdf_writer_proxy --source http://gems.rubyreport.org
Ruport e RubyOnRails
Per integrare dati di applicazioni RubyOnRails all'interno dei nostri report usiamo il modulo acts_as_reportable che usa i risultati di un ActiveRecord::Base.find()
per preparare una tabella di Ruport (Ruport::Data::Table
). Integrandosi con ActiveRecord è naturalmente utilizzabile in modo indipendente da Rails ed in tutte le applicazioni che usano ActiveRecord in modo standalone.
Aggiungiamo require 'ruport'
nel model ActiveRecord oppure direttamente nel file di Rails environment.rb
che provvederà a caricare Ruport automaticamente.
Il modo più semplice per iniziare ad usare i risultati di una ricerca in DB è aggiungere il metodo acts_as_reportable
all'interno della definizione della model class di ActiveRecord. In questo modo si avrà già a disposizione il metodo report_table
.
Listato 6: acts_as_reportable
class Album < ActiveRecord::Base acts_as_reportable belongs_to :artist def artist_name artist.name end end class Artist < Active::Record::Base acts_as_reportable has_many :albums end puts Album.report_table(:all, :except => :artist_id)
Riportiamo alcune opzioni disponibili con il metodo report_table:
Poiché acts_as_reportable
usa il metodo find
di ActiveRecord, tutte le opzioni che non vengono automaticamente riconosciute sono passate al metodo find
. Questo significa che è possibile usare tutte le opzioni riconosciute da ActiveRecord::Base.find
.
puts Album.report_table( :all, :only => :title, :include => { :artist => { :only => :name } } :filters => lambda {|r| r["id"] > 1 })
Equivalente a:
puts album.report_table(:all, :only => [:title], :methods => [:artist_name], :filters => lambda {|r| r["id"] > 1 })
Il metodo di acts_as_reportable
prende tutte le opzioni del metodo report_table
, permettendo così di impostare le opzioni di default per i nostri report:
acts_as_reportable :except => [:id, :artist_id]
Analogamente al metodo ActiveRecord::Base.find_by_sql
è presente il metodo report_table_by_sql
.
Un'altra modalità messa a disposizione da Ruport per collezionare i dati è quella di usare la classe Ruport::Query
, che fornisce il supporto all'uso della libreria RubyDBI
Listato 7. Query
require 'rubygems' require 'ruport' require 'active_record' require 'ruport/acts_as_reportableì Ruport::Query.add_source( :default, :user => "testuser", :passwrod => "testpwd" :dsn => "dbi:mysql:cdArchive_db") query = Ruport::Query.new("SELECT * FROM albums") puts query.result
Inserire grafici ed immagini
La creazione di grafici a partire dai dati presenti nelle strutture base di Ruport richiede l'installazione di ruport-util
e non molto effort. Ruport fa da wrapper per alcune porzioni della libreria Gruff
per i formati JPEG e PNG e di Scruffy
per l'SVG. Risulta quindi necessario provvedere all'installazione di queste librerie, ricordandoci della dipendenza di Gruff
da RMagick
.
È presente una struttura dati apposita per i grafici, nello specifico, Ruport::Data::Graph
, figlia di Ruport::Data::Table
, con l'aggiunta di metodi specifici per la preparazione di grafici, come series
che permette di aggiungere linee ai grafici. A livello di Kernel è presente lo shurtcut Graph
.
Come detto in precedenza, porzioni delle librerie Gruff
e Scruffy
sono presenti direttamente in Ruport. Ad esempio usando Gruff è possibile creare soltanto grafici a linee. Questa non è affatto una limitazione. Sfruttando il concetto di classi aperte di Ruby, basta semplicemente aggiungere il tipo di grafico desiderato (pie, bar, etc.) a quello già presente. Vediamo come aggiungere il tipo grafico pie (torta) e bar.
Listato 8. Estensione del modulo Graph
%w[rubygems ruport ruport/util ruport/extensions].each {|lib| require lib} class Ruport::Formatter module Graph class Gruff < Ruport::Formatter renders [:png,:jpg], :for => Ruport::Controller::Graph def build_graph height = "#{options.height || 600}" width = "#{options.width || 800}" dimensions = "#{width}x#{height}" type = options.graph_type || :line graph = case type when :line ::Gruff::Line.new(dimensions) # inizio aggiunta # fine aggiunta
Listato 9. Applicare l'estensione al controller
class ReportController < Ruport::Controller stage :long_report required_option :file ## # L'hook method setup viene richiamato dopo che le opzioni # sono processate. Questo significa che possiamo manipolare # in modo semplice gli attributi dei dati così come ogni # opzione passata al controller. ## # do someting # PDFReport # ReportController # Dummy Test
Personalizzare i template
Con la definizione di template propri è possibile specificare delle opzioni per la formattazione dei nostri report. La creazione di un nuovo template avviene usando il metodo Ruport::Formatter::Template.create
mentre, per la scelta del template da usare in fase di rendering, si specifica l'opzione :template
quando si crea l'output. È anche possibile estendere un template figlio a partire dal padre.
Facciamo un esempio di quanto appena detto, definiamo un template che chiamiamo simple
.
Listato 10. Definire un template
Ruport::Formatter::Template.create(:simple) do |format| format.page = { :layout => :landscape, :size => "LETTER" } format.text = { :font_size => 12, :justification => :center} end
Ora vediamo come estedere il template simple
Listato 11. Estendere un template
Ruport::Formatter::Template.create(:derived, :base => :simple) do |format| format.table = { :font_size => 10, :heading_font_size => 10, :maximum_width => 720, :width => 720 } format.grouping = { :style => :separated } format.column = { :alignment => :right } format.heading = { :alignment => :right, :bold => true } end class Ruport::Formatter::Text def apply_template options.note = template.note end end ... puts AlbumController.render_text(:data => data, :template => :simple) puts AlbumController.render_text(:data => data, :template => :derived)
Documatic - report in OpenOffice
Si tratta di un progetto indipendente che fornisce un'utile estensione OpenDocument a Ruport. Come si legge dalla home page del progetto, la versione attuale (0.2.0) supporta i formati testo (odt
) e foglioelettronico (ods
).
L'utilizzo di base è molto semplice. Basta creare un template con OpenOffice come quello in figura, preso dalla sezione tutorial del sito del progetto

ed uno script ruby che si occupa di creare i dati, richiamare il template e passargli i dati.
Listato 5. Popolare il template con i dati
require 'rubygems' require 'documatic' # definizione della tabella # utilizziamo Documatic # per generare un OpenDocument in formato testo # ed un foglio elettronico
Rope
Rope è un tool che offre una serie di semplici utilities per la generazione di progetti di reportistica. Genera la struttura di base di una applicazione Ruport, fornendo un valido aiuto agli sviluppatori. Viene installato automaticamente con l'installazione di ruport-util
.
Per iniziare con un nuovo progetto basta lanciare il comando $rope testprj
(pressoché l'equivalente di quanto fa il comando rails
a livello di struttura del progetto).
$ rope testprj creating directories.. testprj/test testprj/config testprj/output testprj/data testprj/data/models testprj/lib testprj/lib/reports testprj/lib/controllers testprj/sql testprj/util creating files.. testprj/lib/reports.rb testprj/lib/helpers.rb testprj/lib/controllers.rb testprj/lib/templates.rb testprj/lib/init.rb testprj/config/environment.rb testprj/util/build testprj/util/sql_exec testprj/Rakefile testprj/README
Il file config/environment.rb
contiene le configurazioni per il progetto generato con rope (es. database
, mailer
).
Generazione di un modello con rope
$ util/build model album model file: data/models/album.rb class name: Album $ cat data/models/album.rb class Album < ActiveRecord::Base acts_as_reportable end
Conclusioni
L'utilizzo di Ruport e delle sue estensioni rende la generazione di report particolarmente semplice. In definitiva si tratta di un tool ben fatto, estendibile e ben documentato.