Ruby on Rails framework for making reporting easy.
Usage
Compendium is a reporting framework for Rails which makes it easy to create and render reports (with charts and tables). Compendium requires at least Ruby version 2.2.
A Compendium report is a subclass of Compendium::Report
. Reports can be defined using the simple DSL:
class MyReport < Compendium::Report
# Options define which parameters your report will accept when being set up.
# An option is defined with a name, a type, and some settings (ie. default value, choices for radio buttons and
# dropdowns, etc.)
option :starting_on, :date, default: -> { Date.today - 1.month }
option :ending_on, :date, default: -> { Date.today }
option :currency, :radio, choices: [:USD, :CAD, :GBP]
# By default, queries are converted to SQL and executed instead of returning AR models
# The query definition block gets the report's current parameters
# totals: true means that the last row returned should be interpretted as a row of totals
query :deliveries, totals: true do |params|
Items.where(delivered: true, purchased_at: (params[:starting_on]..params[:ending_on]))
end
# Define a filter to modify the results from specified query (in this case :deliveries)
# For example, this can be useful to translate columns prior to rendering, as it will apply
# for all render types (table, chart, JSON)
# Note: A filter can be applied to multiple queries at once
filter :deliveries do |results, params|
results.each do |row|
row['price'] = sprintf('$%.2f', row['price'])
end
end
# Define a query which collects data by using AR directly
query :on_hand_inventory, collect: :active_record do |params|
Items.where(in_stock: true)
end
# Define a query that works on another query's result set
# Note: chart and data are aliases for query
chart :deliveries_over_time, through: :deliveries do |results|
results.group_by(&:purchased_at)
end
# Queries can also be used to drive metrics
metric :shipping_time, -> results { results.last['shipping_time'] }, through: :deliveries
end
Reports can then also be simply instantiated (which is done automatically if using the supplied
Compendium::ReportsController
):
report = MyReport.new(starting_on: '2013-06-01')
report.run(self) # The parameter is the context to run the report in; usually this should be
# a controller context so that methods like current_user can be used
Compendium also comes with a variety of different presenters, for rendering the setup page, and displaying charts
(report.render_chart
), tables (report.render_table
) and metrics for your report. Charting is delegated through a
ChartProvider
to a charting gem (amcharts.rb is currently supported).
Report Options
Report options are defined by the keyword option
in your report class. Options must have a name and a type (scalar, boolean, date, dropdown or radio). Additionally, an option can have a default value (given by a proc passed in with the default:
key), and validations (via the validates:
key).
In order to specify parameters for the options, pass a hash to MyReport.new
. Parameters are available via params
:
r = MyReport.new(starting_on: Date.today - 3.months, ending_on: Date.today)
r.params
# {
# "starting_on"=>Sun, 30 Aug 2015,
# "ending_on"=>Mon, 30 Nov 2015,
# }
Validation
If validation is set up on any options, calling valid?
on the report will validate any given parameters against the validations set up, and will populate an errors object. All validations provided by ActiveModel::Validations
are available.
class MyReport < Compendium::Report
options :starting_on, :date, validates: { presence: true }
end
r = MyReport.new
r.valid?
# => false
r.errors
# => #<ActiveModel::Errors:0x007fe8359cc6b8
# @base={"starting_on"=>nil},
# @messages={:starting_on=>["This field is required."]}>
Query types
Compendium provides a few types of queries in order to make report writing more streamlined.
Through Queries
A through query lets you use the results of a previous query (or multiple queries) as the basis of your query. This lets you build on another query or combine multiple query's results into a single query. It it specified by passing the through:
key to query
, with a query name or array or query names (as symbols).
query :dog_sales { |params| Order.where(pet_type: 'dog', created_at: params[:starting_on]..params[:ending_on]) }
query :cat_sales { |params| Order.where(pet_type: 'cat', created_at: params[:starting_on]..params[:ending_on]) }
query :bird_sales { |params| Order.where(pet_type: 'bird', created_at: params[:starting_on]..params[:ending_on]) }
query :total_sales, through: [:dog_sales, :cat_sales, :bird_sales] do |results, params|
# results is a hash with keys :dog_sales, :cat_sales, :bird_sales
end
Count Queries
A count query simplifies creating a query where you want a count (especially per group of something). A count query is specified by adding count: true
to the query
call.
query :sales_per_day, count: true do
Order.group("DATE(created_at)")
end
# results will look something like
# { 2015-10-01 => 4, 2015-10-02 => 20, ... }
Sum Queries
Like a count query, a sum query is useful for performing an aggregate function on a grouped query, in this case summing the results. A sum query is specified by adding sum: :column_name
to the query
call.
query :commission_per_salesperson, sum: 'commission' do
# assume commission is a numeric column
Order.group(:employee_id)
end
# results will be something like
# { 1 => 840.34, 2 => 1065.02, ... }
Collection Queries
Sometimes you'll want to run a collection over a collection of data; for this, you can use a collection query. A collection query will perform the same query for each element of a hash or array, or for each result of a query. A collection is specified via collection: [...]
, collection: { ... }
or collection: query
(note not a symbol but an actual query object).
Tying into your Rails application
Compendium has a Rails::Engine
, which adds a default controller and some views. If desired, the controller can be
subclassed so that filters and the like can be added. The controller (which extends ApplicationController
automatically) has two actions: setup
(collect options for the report) and run
(execute and render the report),
with accompanying views. The setup
view can be included inside your own view using the render_report_setup
method (NOTE: you have to pass local_assigns
into it if you want locals to be passed along).
Routes are not automatically added to your application. In order to do so, you can use the mount_compendium
helper
within your config/routes.rb
file
mount_compendium at: '/report', controller: 'reports' # controller defaults to compendium/reports
Rendering report results in other formats
JSON
While the default action when running a report is to render a view with the results, Compendium reports can be rendered
as JSON. If using the default routes provided by mount_compendium
(assuming compendium was mounted at /report
),
GET
ing or POST
ing to report/report_name.json
will return the report results as JSON. You can also collect
the results of a single query (instead of the entire report) by GET
ing or POST
ing to
report/report_name/query_name.json
.
CSV
A report can be exported as CSV. In order to enable CSV exports, a query needs to be defined as the exporter for the report. Note that only one query can be exported, because otherwise there's no way to ensure that the headings are consistent.
class MyReport < Compendium::Report
exports :csv, :deliveries # Defines `deliveries` to be the query that is exported to CSV
end
Note that if your report class subclasses another, and you want to disable a previously defined exporter, you can with exports :csv, false
.
When a report has a CSV exporter defined, an Export CSV
button will appear on the default setup page. You can also directly export
using the path /report/:report_name/export.csv
(using GET
or POST
).
Customization of the query can be done by setting table options for the query. See the Rendering a table section below for more details.
Displaying Report Results
Chart Providers
As of 1.1.0, chart providers have been extracted out of the main repository and are available as their own gems. If you want to render queries as a chart, a chart provider gem is needed.
If multiple chart providers are installed, you can select the one you wish you use with the following initializer:
Compendium.configure do |config|
config.chart_provider = :AmCharts # or any other provider name
end
The following providers are available (If you would like to contribute a chart provider, please let me know and I'll add it to the list):
- compendium-amcharts - makes use of AmCharts.rb
- compendium-highcharts - makes use of lazy_high_charts [thanks to cimtico]
Rendering a table
Note: When table settings are defined for a query, they are applied both to rendering HTML tables, as well as CSV file exports. See Rendering report results in other formats above for more details.
In addition to charts, you can output a query as a table. When a query is rendered as a table, each row is output with columns in the
query order (so you may want to use an explicit select
in your query to order the columns as required). If the query is set up with
totals: true
, a totals row will be added to the bottom of the table.
In order to customize the table, you can add a table
declaration to your report. Each query can have different table settings.
class MyReport < Compendium::Report
table :deliveries do
# The i18n scope to use for any translations can be specified:
i18n_scope 'reports.my_report'
# Column headings by default are the column name passed through I18n,
# but can be overridden:
# ... with a block...
override_heading do |heading|
# ...
end
# ... or one at a time...
override_heading :col, 'My Column'
# Records where a cell is 0 or nil can have the value overridden to something else:
display_zero_as 'N/A'
display_nil_as 'NULL'
# You can specify how to format numbers:
number_format "%0.1f"
# You can also specify formatting on a per-column basis:
format(:col) do |value|
"#{(value / 50) * 100}%"
end
end
end
A query is rendered from a view, and is passed in the view context as the first parameter. Optionally, a block can be passed to override previously defined settings:
my_query.render_table(self) do
display_zero_as 'nil' # Override the previous version just for this render
end
CSS Classes
By default, Compendium uses the following four CSS classes when rendering a table:
Element | Element Type | Class Name |
---|---|---|
Table | table |
results |
Table header | tr |
headings |
Table data | tr |
data |
Table footer (totals) | tr |
totals |
Each class can be overridden when setting up the table:
my_query.render_table(self) do |t|
t.table_class 'my_table_class'
t.header_class 'my_header_class'
t.row_class 'my_row_class'
t.totals_class 'my_totals_class'
end
Interaction with other gems
- If accessible_tooltip is present, option notes will be rendered in a tooltip rather than as straight text.
Installation
Add this line to your application's Gemfile:
gem 'compendium'
And then execute:
$ bundle
Or install it yourself as:
$ gem install compendium
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request