Using JRuby to integrate Ruby with Java

Riding Rails


If you're interested in Ruby but you don't want to give up the benefits of Java, try the Java-friendly JRuby interpreter.

By Ramon Wartala

Nikolay Kropachev, Fotolia

The Ruby language and the Rails framework work together to provide a popular option for rapid development of web applications. Rails speeds development by defining much of the program's structure with minimal input from the programmer. Despite the recent success of Ruby on Rails, however, many developers still prefer Java. The Java environment offers several advantages for the developer, including more sophisticated parallel processing and better Unicode support. In some cases, the development team might prefer to stay with Java simply for compatibility with existing Java libraries and legacy program components.

Bridge to Java

JRuby [1] is a Ruby interpreter that offers a bridge from Ruby to the world of Java. According to the website, the JRuby package provides the following:

Getting Started

If you're partial to Java but intrigued by the potential of Ruby on Rails, JRuby might be an interesting option. In this article, I will help you get started with JRuby and offer a look at some benefits of blending Java and Ruby.

Jan Arne Petersen, a student at the University of Bonn, Germany, create the first Java port of Ruby 1.6 back in 2001. To be able to use the Rails framework natively on JRuby, Thomas Enebo and Charles Nutter looked into porting Ruby 1.8 in 2006.

The project started to pick up speed in September of the same year, when Sun Microsystems signed up the two developers and assigned them full-time to developing JRuby.

If you already have a Java SDK on the target system, JRuby is easy to install on Linux. The various releases are available in TAR or ZIP archive format. For this article, I used the first beta release of the 1.1 version throughout.

After unpacking the archive where you can reference it with the JRUBY_HOME environmental variable, you can test the installation with a couple of sample programs that reside below the samples directory. The line

jruby $JRUBY_HOME/samples/swing2.rb

should draw a simple Swing frame with a button. After negotiating this step, you can start to add critical components. Out of the box, Ruby includes its own package management tool, RubyGem. Typing

jruby -S gem list r -r

for example, searches gem.rubyforge.org for packages starting with the letter r. Typing

jruby -S gem help commands

gives an overview of available commands.

jruby -S gem install rake

installs the rake build tool, which plays the role of the classical Unix make utility under Ruby.

jruby -S gem install -y rails

installs the current version of the complete Rails framework with a full set of packages to resolve dependencies. If you want to work with databases, it makes sense to use the JRuby ActiveRecord JDBC package, which was programmed by the core JRuby developers.

jruby -S gem install ActiveRecord-JDBC

completes the installation of the required components. The packages are all neatly organized below $JRUBY_HOME/lib/ruby/gems/1.8/gems.

Rails in JRuby

In familiar Ruby style, you can create a Rails project on JRuby as follows:

jruby -S rails mybooks

The new mybooks directory will now contain the complete skeleton of the new web application. However, it lacks some flesh on its bones, which is easy to change by creating the following entity, referred to as a model in Rail-speak:

jruby script/generate model Book

Besides a couple of other files, the Rails generator now creates a file called 001_create_books.rb in the db/migrate directory for the sample application (Listing 1).

Listing 1: create_books.rb
01 class CreateBooks < ActiveRecord::Migration
02   def self.up
03     create_table :books do |t|
04         t.column :title, :string
05         t.column :isbn, :string
06     end
07   end
08
09   def self.down
10     drop_table :books
11   end
12 end

The database migration file in Listing 1, which is used for database schema versioning, contains both the components needed for creating and destroying a specific model table. Lines 4 and 5 specify two model attributes, which point to corresponding columns in the books database table.

Before you can test these mappings, you need to perform the only configuration step you can't do with Ruby on Rails: the database configuration. You need a new database for this. On MySQL, you would type:

mysql> CREATE DATABASE mybooks_development;
mysql> GRANT ALL ON mybooks_development.* TO'root'@'localhost' IDENTIFIED BY 'root';

To allow Rails to connect to the database, there is a YAML-formatted configuration file in the config/database.yml directory. Using the ActiveRecord JDBC package, the connector looks like the example shown in Listing 2.

Listing 2: config/database.yml
01 development:
02       adapter: jdbc
03       driver: com.mysql.jdbc.Driver
04       url: jdbc:mysql://localhost/mybooks_development
05       username: root
06       password: root

Also, add a line like

require 'jdbc_adapter'

before

Rails::Initializer.run do |config|

to the environment.rb file in the same directory. To populate the database, type

jruby -S rake db:migrate

to create a new database migration, which you can then try out at the console using the JRuby Console ntool:

jruby script/console

which loads the current MyBooks environment. The commands in Listing 3 test the ActiveRecord object mapper. Typing the following runs the Rails Scaffold Generator:

jruby script/generate scaffold book

Now the Rails application on JRuby is ready to run. Typing

jruby script/server

starts the WEBrick HTTP server, which plays the role of a lightweight developer server in Rails. After starting the server, look at the C(reate)R(etrieve)U(pdate)D(delete) interface for the Book model at http://localhost:3000/books.

Listing 3: ActiveRecord Test
01 >> b = Book.new
02 => #<Book:0xfc01db @attributes={"isbn"=>nil, "title"=>nil}, @new_record=true>
03 >> b.title = "Web Applications with Ruby on Rails"
04 => "Web Applications with Ruby on Rails"
05 >> b.isbn = "9783827324917"
06 => "9783827324917"
07 >> b.save

Java in Rails

So far, the differences between Ruby and JRuby have been slight, with just a few changes to the database connection. Once the system is running, you can easily deploy self-programmed and third-party Java libraries in this extremely agile environment. For the purposes of the Book example, a simple Java class [2] will do just fine; it tests the validity of ISBN numbers.

The isbn.jar Java archive is copied to the same location as the calling model class below app/models. The code from Listing 4 is added to the Book model class.

Listing 4: Book Extension
01 require 'java'
02 require 'isbn.jar'
03
04 class Book < ActiveRecord::Base
05   def validate
06     unless validate_isbn
07       errors.add('isbn','is invalid')
08     end
09   end
10
11   def validate_isbn
12     begin
13       isbn = com.openly.info.ISBN.new(self.isbn)
14     rescue
15       isbn = nil
16     end
17   end
18 end

The first two lines bind both Java and the ISBN Java package. Within the model class, the ISBN Java constructor is called against the model instance's isbn attribute. This call to the ISBN constructor occurs in the validate_isbn method. Ruby exception handling prevents the application from throwing wild Java exceptions when it finds an invalid ISBN number.

Validation methods are an approach to giving the Rails application a way to respond to invalid user input. The program typically executes the validate method before storing the object instance. You can monitor this behavior in the Rails Console (Listing 5).

Listing 5: Error Messages at the Rails Console
01 >> b = Book.new(:title => 'No Book', :isbn => '4712')
02 => #<Book:0xd849e0 @attributes={"isbn"=>"4712", "title"=>"No Book"}, @new_record=true>
03 >> b.save
04 => false
05 >> b.validate
06 => ["is invalid", "is invalid"]

Of course, this kind of validation is also possible in Ruby itself, but this simple example shows how quickly and easily you can integrate Java code with (J)Ruby on Rails.

Rails Delivers WARs

A combination of Apache and multiple Mongrel instances [3] provides a standard approach for delivering Ruby on Rails applications. Configuring an environment of this kind is non-trivial and will probably put off any experienced J2EE developer, especially if you are used to dropping your application in the form of a Web Application Archive (WAR) onto any J2EE-compatible application server and letting it auto-deploy. Thanks to JRuby, the WAR technique is now open to Rails applications. As of this writing, you do need to install the GoldSpike plugin for your own application, as follows:

jruby script/plugin install http://jruby-extras.rubyforge.org/svn/trunk/rails-integration/plugins/goldspike

Before you can create the WAR archive, you typically need a valid database connector to support the process. GoldSpike creates a war.rb file in the config directory for settings of this type.

Assuming a MySQL connector, deleting the pound sign (#) in the following line should do the trick:

#maven_library 'mysql', 'mysql-connector-java', '5.0.4'

Then call the following Rake target:

jruby -S rake war:standalone:create

You can monitor the archives that have been added to the WAR file at the command line. If you have Apache Tomcat [4] as your J2EE container, simply type

cp mybooks.war $TOMCAT_HOME/webapps/.

to launch into auto-deployment. The Rails application is installed at http://localhost:8080/mybooks/books.

Bridges Between Worlds

In this article, I presented a quick view of the possible deployment scenarios for JRuby on Rails. Developers interested in wandering between the worlds might also appreciate the ability to use JRuby code within Java. This feature is really exciting if you want to use a session bean to offer a web service that retrieves data from a Rails application.

Some developers argue that Java/Ruby interaction options like those described in this article simply dilute the Ruby or Java model. Many real-world projects, however, need a means for integrating the agile development philosophy embodied in a framework like Rails with legacy applications or expensive, proprietary libraries. Java developers also will have an easier time adjusting to the Ruby on Rails environment if they can take their tried-and-trusted tool chains with them.

INFO
[1] JRuby: http://jruby.codehaus.org
[2] ISBN Tools: http://isbntools.com
[3] Mongrel: http://mongrel.rubyforge.org/index.html
[4] Apache Tomcat: http://tomcat.apache.org
THE AUTHOR

Ramon Wartala is Head of IT with online marketers orangemedia.de based in Hamburg, Germany, and the co-author of Web Applications with Ruby on Rails, which is published by Addison-Wesley.