Ruby
Article
By Devdatta Kane

Create Great Reports with JasperReports

By Devdatta Kane

JasperReports_Logo

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:

jaspersoft-studio

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.

final-report

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.

  • Houston Crockett

    Will you please show an example using report_params? I keep getting syntax errors in the query at `$P{DateStart}` etc.

    • Penh Lenh

      select * from orders where order_date between ‘2015-09-29 10:18:00’ and ‘2015-09-29 11:29:49’

      I passed sql statement like this, it’s working just fine.

  • Houston Crockett

    Also, *thank you kindly* for the working tutorial. This is very helpful to me. I just can’t get parameters to work.

    • Devdatta Kane

      Hi,

      I am glad that the tutorial was helpful. As for the parameters issue, the code I have shown does not support parameters in report query, since we have directly passed a JDBC ResultSet to the report instead of using the report’s query. Parameters passed via the report_params are intended for other usecases such as title, username or any other static data.

      You can modify the Report class to use report’s query and parameters by passing a JDBC connection instead of the JDBC ResultSet. Like this

      fill = JasperFillManager.fill_report(report_source, params, @conn)

      I hope this helps.

      • Houston Crockett

        Another developer on my team is building the .jasper files, and I’m implementing them in the rails app. I think he wants to use parameters to provide date ranges. This is very confusing to me. Can you link to a tutorial which demonstrates how to use parameters?

      • Houston Crockett

        My teammate sends me .jasper files with the query and the parameters in the file. Then I convert them to use the above methods. It seems counterintuitive and cumbersome.

    • I plan on making a similar post as this bring attention to the API features of JR Server while also utilising parameters to modify a report’s output.

      • Houston Crockett

        Yes, please do.

      • Houston Crockett

        Yes, please do.

  • Sertan Gulvrn

    hi, i completed all the steps. but report query not working.
    example my controller code is: respond_to_report(‘users’, ‘SELECT * FROM users’, ‘users.pdf’)
    report file is generated but users dont listed on report. im trying this query on jaspersoft studio and results is true. can you help me?

  • LOL

    jdbc:datadirect:openedge://localhost:port;databaseName;defaultSchema can anyone know how to modified this using rest request ?

Recommended
Sponsors
Get the latest in Ruby, once a week, for free.