Create Great Reports with JasperReports
Ruby on Rails is an amazing framework for building web applications. The Ruby and Rails ecosystems are very active and mature, with many libraries for solving the various problems encountered by developers. But one particular area where I feel Ruby lacks options is reporting. Many solutions exist in Rails for generating reports, most of them rely on generating HTML and converting to PDF. This approach has its advantages, but falls apart for complex and pixel perfect reports.
Enter JasperReports. JasperReports is a very popular open source reporting library widely used in Java world. Many of you may be curious about how to use a Java library in Rails. There are many approaches, but the one I prefer is to use JRuby. This requires you to convert your application to JRuby, which is quite simple to do these days. So, let’s get started.
Sample Rails Application
We will create a sample Rails application with a contact model and CRUD actions. The application will use JRuby, Rails 4.2, and SQLite as database to keep things simple. First of all, install JRuby with RVM or rbenv.
Switch to JRuby and bundle install
the latest Rails gem. Now create a new Rails application, like so:
rails new contactbook
After the application is generated, create a Contact scaffold, which has amodel and CRUD resource operations:
cd contactbook
rails g scaffold Contact name:string address:string city:string phone:string email:string
Now migrate the database:
rake db:migrate
Let’s check how its working:
rails s
Point your favorite browser to http://localhost:3000/contacts and make sure everything is working properly. Create some records so we can use those later.
A Sample JasperReport
Let’s create a simple report that shows list of contacts. Download the JasperSoft Studio suitable to your platform from the Jaspersoft Studio site. Install and run Jaspersoft Studio. It is full fledged IDE based on Eclipse, specifically to design JasperReports. It has a learning curve, for sure, but we will not get into those details for now. You can refer to Jaspersoft Studio – Resources for more information.
Create a report named contacts
using Jaspersoft Studio which should look as follows:
Compile the report and paste the contacts.jasper file into app/reports/ in our Rails application.
Integrating JasperReports
Now comes the important part, integrating JasperReports into the Rails application. We will be using JasperReports 6.1 version for this tutorial. Head over to the JasperReports Library page and download jasperreports-6.1.0-project.tar.gz
. This archive contains the JasperReports JAR files in its dist folder, along with dependencies in lib folder. Copy all the files from both of those folders and put them in a folder named jasperreports under the lib directory in our Rails application.
contactbook
- app
...
- lib
- jasperreports
- jasperreports-6.1.0.jar
...
We have all required files for JasperReports in place, so let’s create a class to integrate JasperReports. Create a file called jasper_report.rb in lib directory with following code:
Dir.entries("#{Rails.root}/lib/jasperreports").each do |lib|
require "jasperreports/#{lib}" if lib =~ /\.jar$/
end
require 'java'
java_import Java::net::sf::jasperreports::engine::JasperFillManager
java_import Java::net::sf::jasperreports::engine::JasperExportManager
java_import Java::net.sf.jasperreports.engine.JRResultSetDataSource
class JasperReport
DIR = "#{Rails.root}/app/reports"
def initialize(report, query, params = nil)
@model = report
@report_params = params
@conn = ActiveRecord::Base.connection.jdbc_connection
@query = query
end
def to_pdf
stmt = @conn.create_statement
@result = JRResultSetDataSource.new(stmt.execute_query(@query))
report_source = "#{DIR}/#{@model}.jasper"
raise ArgumentError, "#@model does not exist." unless File.exist?(report_source)
params = {}
params.merge!(@report_params) if @report_params.present?
fill = JasperFillManager.fill_report(report_source, params, @result)
pdf = JasperExportManager.export_report_to_pdf(fill)
return String.from_java_bytes(pdf)
end
end
Let’s see what the code actually does. First we require all JasperReports files into our class, like so:
Dir.entries("#{Rails.root}/lib/jasperreports").each do |lib|
require "jasperreports/#{lib}" if lib =~ /\.jar$/
end
Add the Java import declarations for JasperReports:
require 'java'
java_import Java::net::sf::jasperreports::engine::JasperFillManager
java_import Java::net::sf::jasperreports::engine::JasperExportManager
java_import Java::net.sf.jasperreports.engine.JRResultSetDataSource
Define the location where all reports will be stored:
DIR = "#{Rails.root}/app/reports"
Add the initialization code in the class constructor:
def initialize(report, query, params = nil)
@model = report
@report_params = params
@conn = ActiveRecord::Base.connection.jdbc_connection
@query = query
end
As a first step, we initialized all required variables:
- The report’s file name –
report
- The report’s SQL query –
query
- Any optional parameters to pass on to the report –
params
@conn
is a JDBC connection from the ActiveRecord connection pool, since JasperReports requires a JDBC connection to execute the query.
Finally, we have added a method to fill and export the report to PDF format:
def to_pdf
stmt = @conn.create_statement
@result = JRResultSetDataSource.new(stmt.execute_query(@query))
report_source = "#{DIR}/#{@model}.jasper"
raise ArgumentError, "#@model does not exist." unless File.exist?(report_source)
params = {}
params.merge!(@report_params) if @report_params.present?
fill = JasperFillManager.fill_report(report_source, params, @result)
pdf = JasperExportManager.export_report_to_pdf(fill)
return String.from_java_bytes(pdf)
end
The first item is a JDBC statement, stmt = @conn.create_statement
, which returns a JDBC ResultSet by executing @result = JRResultSetDataSource.new(stmt.execute_query(@query))
. The report (.jasper
) file created earlier using JasperStudio is set to the report_source
. After that, invoke the JasperFillManager.fill_report
to fill the report with the provided ResultSet and parameters. Lastly, invoke JasperExportManager.export_report_to_pdf
to export the report into a PDF bytestream which is returned using String.from_java_bytes
.
We now have the report as a PDF bytestream, but we’ve no way to send it to user, yet. So, add a small helper method in application_controller.rb as follows:
def respond_to_report(name, query, filename, download = false, report_params = nil)
@report = JasperReport.new(name, query, report_params)
disposition = (download.nil? || download == false) ? 'inline' : 'attachment'
send_data @report.to_pdf, :filename => filename, :type => :pdf, :disposition => disposition
end
This helper method simplifies the report invocation and sends the response back to the user. There are also added options for providing a filename, along with a disposition
option to open the file within the browser or download it as an attachment.
Now, we will to add a action to the ContactsController
that calls the report. Add the following code to app/controllers/contacts_controller.rb:
def report
respond_to_report('contacts', 'select * from contacts', 'contacts.pdf')
end
As we have no parameters to pass and want to open the report in browser, we have skipped the download
and report_params
from the method call.
Update routes.rb to add the new action:
resources :contacts do
get :report, on: :collection
end
Add a link for the report in the contacts/index.html.erb view:
...
<h1>Listing Contacts</h1>
<%= link_to 'Download as PDF', report_contacts_path %>
...
Fire up the server and go to http://localhost:3000/contacts. Click on ‘Download as PDF’. You should now see a PDF file with all contacts listed.
Wrapping Up
This was a quick primer on how to integrate JasperReports with Rails to create reports without much effort. There is much more that can be done with JasperReports and Rails, such as search criteria, complex reports, subreports, etc. But that’s for another tutorial.
I hope you enjoyed this post. Your comments and insights are always welcome.