JRuby Helps Us Craft Quality Software

“It's kind of a Frankenstein.” We’ve used this phrase more than a few times to describe the architecture and technology used to design a product that was recently released after almost a year of development by the software craftsmen of 8th Light.

The product is a multi-platform, PC management application for library computer labs written primarily in Ruby, run on the JVM with JRuby. The user interface is written in erb-templated HTML and Javascript, and is displayed in an embedded web browser.

JNA is used to call operating system specific functions that enable an insane amount of control over the desktop. The various JRuby components communicate with each other over DRb.

But the most amazing thing about this project is that we are using JRuby to deliver a portable, rich desktop app using the best of web technologies. We chose JRuby because it increased portability and performance and reduced development time.

At 8th Light we had tried before to build products for our clients using Ruby, but we always hit a dead end when it came to deployment.

For our clients who provide software products instead of software services, the applications must install easily and completely on the end customers machines while still protecting the intellectual property of our client.

We tried very hard to make technologies like RubyScript2exe work, but in the end a packing and unpacking solution was too slow and still left our client’s source code open for the reading on the installed computer.

As we began the project about a year ago, we discovered that JRuby had reached the point where it could be a viable platform for us to be able to build products that our client’s customers could easily deploy in their own environments.

Our solution was to bundle all of our JRuby code into jars after it had been encrypted. By patching into the JRuby load service we were able to decrypt the source on the fly and leave the code bundled and encrypted on the disk at all times.

JRuby allows us dirt simple access to Java’s huge collection of libraries, along with plenty of custom Java libraries our client had already written. We also go back the other way, implementing Java interfaces in JRuby and using those objects in Java code.

This fluidity allows us to always pick the best tool for the job. Early in the project, this also helped to sell the technology to our client.

They were wary of a new language that none of their in-house developers knew, but we were able to reassure them that they could easily drop back into Java at any time.

At one point in the project, the client’s development team jumped on the project to add a new configuration interface to the server side of the application.

The team wanted to use a set of Java tools that they were already familiar with, but needed to make calls into the JRuby side of the application to get and set the configuration values.

We built a new Java interface for them and implemented it in JRuby—then from our Ruby initialization code, called a static Java function back on the Main class, passing in the configuration object.

This allowed the client's team to initialize their java objects with the instance of our JRuby class that we had provided.

1 //The Java Interface
2 public interface ConfigurationStore
3 {
4  void put(Map<string> pairs);
5  void put(String key, String value);
6  String get(String name);
7 }
 1 #The Ruby Implementation
 2 class ConfigurationStore
 3  include Java::package.ConfigurationStore
 4   def get(key)
 5   .....
 6   end
 7 # Notice how we were able to implement the function 
 8 # overloading present in the interface by
 9 # using a variable arguments in one ruby method.
10  def put(*args)
11   if args.size == 2
12    ....
13   elsif args.size == 1
14    .....
15   else
16    raise ArgumentError.new("put() takes a Hash
17                 (or a Java Map) or a key and value pair")
18   end
19  end
20 end

We wanted to be extra sure that the class we were writing in Ruby would work from Java before we handed it over, so in addition to the rspecs we wrote to test the Ruby class, we also wanted to write a JUnit Test.

Normally, we inject a JRuby object into Java by calling a Java function from a JRuby method and passing along the JRuby object that is to be used. In this case we wanted to actually instantiate the Ruby object in the setup of a JUnit test.

We dug around in the JRuby APIs and were able to leverage the fact that the RubyRuntimeAdapter returns the last thing evaluated.

We were able to use the toJava() method on the IRubyObject returned from eval and cast the result into the interface class. Then we were free to write JUnit tests exercising the functionality of the JRuby class.

1 // instantiating a Ruby object in Java
2 RubyInstanceConfig config = new RubyInstanceConfig();
3 Ruby runtime = JavaEmbedUtils.initialize(new ArrayList(), config);
4 RubyRuntimeAdapter evaler = JavaEmbedUtils.newRuntimeAdapter();
5 IRubyObject result = evaler.eval(runtime, "require 'configuration_store';" +
6                                 "config_store = ConfigurationStore.new"); 
7 store = (ConfigurationStore) result.toJava(ConfigurationStore.class);

We do a lot of things at 8th Light to ensure that we can deliver very high quality code to our clients as fast as is responsible. One of the most important things we do is work in highly productive languages.

JRuby enables us to write most of the complicated business logic of our application in the highly productive Ruby language and still provide a viable deployment scheme that allows our clients to successfully sell their product to their customers.

This article appears in the April 6th, 2010 Engine Yard Newsletter

Doug Bradbury, Software Craftsman

Doug Bradbury is a maker, a thinker, a craftsman, and a learner.