torsdag, augusti 17, 2006

The JRuby Tutorial #1: Getting started

This is the first installment in a series of tutorials I will write about how to use JRuby for fun and profit. I'm planning on covering a wide range of things you can do with JRuby, from Web frameworks with JDBC to writing your own JRuby extensions in Java. This time I'll take some time covering the basics; what you can do and how you can do it.

I'm writing these tutorials on Windows 2003, but most operations are not OS-centric, so hopefully people with other systems can use these instructions unchanged.

This tutorial will not teach neither Java or Ruby. A basic knowledge about both programming languages are assumed, but most examples will be in Ruby and focus on Ruby development with help from Java (the issue on JRuby extensions is a notable exception.)

What is JRuby
To quote the homepage: "JRuby is a 100% pure-Java implementation of the Ruby programming language." Almost complete support for all Ruby constructs work, and the idea is that you should be able to just use your Ruby scripts with JRuby instead of MRI (Matz' Ruby Implementation, the original Ruby, written in C). But the goal goes beyond that. Java is a powerful platform and there are millions of lines of Java code being written each month, that the world will have to live with for a long time from now. By leveraging Java the platform with the power of the Ruby programming language, programmers get the best from both worlds.

Limitations and caveats
Of course, nothing is simple, so JRuby can't do everything that MRI can. Some of these limitations are due to basic constraints in the Java platform. For example, JRuby will never support a fine-grained timed, until Java does it. Some File-operations just won't work through the Java API. Other limitations are due to differences in implementation, and will probably go away Any Time Now (TM). Some of these include the lack of support for continuations (which Ruby 2.0 won't support anyway), that JRuby's threads are real operating system threads instead of Green Threads (which Ruby 2.0 will have too). Some problems exist with extending Java classes from Ruby and having the result show up from Java. These issues are under development and will be available as soon as enough man hours get put on the problem.

Downloading and installing
The first step in downloading JRuby is to decide whether you should go with a released version (which would be 0.9.0 at this time of writing), or run JRuby from trunk. The last few months, development on JRuby have been quite hectic. Much has been done, and trunk already is quite far away from 0.9.0, both in terms of features, performance and bug fixes. This tutorial will be based on trunk, and if there are any special considerations regarding version, this will be noted in the relevant issue.

To download JRuby from trunk, you need to install a Subversion client. One of these can be found at http://subversion.tigris.org and is very easy to install. After you've downloaded and installed Subversion you're ready to decide where you want JRuby to call home. After you've decided this, you have to set an environment variable called JRUBY_HOME to this directory. For example, I would do this:
set JRUBY_HOME=d:\project\jruby-trunk
in a command window. (In the long run, it would be better to put this somewhere so that it always get set. In Win32 I go to the System configuration on my Control Panel, choose the Advanced tab and press Environment Variables.)

Now we're ready to check out the latest JRuby version:
svn co svn://svn.codehaus.org/jruby/trunk/jruby %JRUBY_HOME%
After you have downloaded JRuby you have to build it, so you have to have a recent version of Ant installed. To build and test JRuby, just enter these commands:
cd %JRUBY_HOME%
ant test
The testing will take a while, but it's good to make sure your installation is fully functional. After you've got the final "BUILD SUCCESSFUL", you can be reasonably sure that your JRuby is functional.

Using Java from Ruby
OK, so now that we've got JRuby up and running, we should try a simple example to see that it actually works correctly and that we can use Java classes to. Write this into a file called testJava.rb:
require 'java'
include_class 'java.util.TreeSet'
set = TreeSet.new
set.add "foo"
set.add "Bar"
set.add "baz"
set.each do |v|
puts "value: #{v}"
end

and run with the command
jruby testJava.rb
You will get this output:
value: Bar
value: baz
value: foo
So, what's happening in this small script is that we first include JRuby's java-support. This provides lots of interesting capabilities, but the most important one is the method Kernel#include_class, which is used to get hold of a Java-class. In most cases you can just include the classes you're interested in and use them as is. In our case, we instantiated the TreeSet as usual in Ruby, by calling new on the class. After this we added some strings to the set, and since JRuby automatically transforms Ruby-Strings into Java Strings, these get sorted by TreeSet in ASCII-order. You may have noticed that we could use a standard each-call to iterate over the entries in the set. This is one of the nice features of JRuby. Even though you're actually working with a Java Collection, it looks and feels (and quacks) like a regular Ruby collection object. All Java classes that implements java.util.Collection automatically gets an each method and includes Enumerable.

The regular include_class function does not work correctly if you try to import a Java class with the same name as an already existing Ruby class. This won't work, for example:
include_class 'java.lang.String'
since there already exists a class called String in Ruby. The first way of handling this problem is to rename the class when including it, by adding a block to the call to include_class:
include_class('java.lang.String') {|package,name| "J#{name}" }
In this case, the class added to the Ruby name space is called JString and doesn't collide with the Ruby String.

The second solution uses the fact that include_class includes the classes mentioned into the current name space, so you can call include_class from inside a module definition, and the classes will be available there:
module Java
include_class 'java.lang.String'
end
The Java String is now available as the class Java::String. If you don't want to include all classes specifically, you can also include a complete package, with include_package. It works exactly like include_class, but takes a package name instead, and makes all classes in that package (but not sub packages) available as classes in the current context.

This is mostly it. JRuby tries as hard as possible to follow the Ruby way, and make Java integration as natural as possible. The ideal is that you should be able to guess how it works, and it will work that way.

Conclusion
The last 6 months, JRuby have become a very real alternative to MRI. As performance gets better and better, the promise of integration with legacy Java systems will get more and more convincing. And using Ruby for tools and tasks where Java only incurs overhead is a very compelling way for Java developers to get the most out of their current tools.

The next issue is slated to be about using Camping from JRuby, together with ActiveRecord and JDBC. Camping is microframework, that together with ActiveRecord lets you write small applications very compact and efficient.

4 kommentarer:

Anonym sa...

Thanks for very useful tutorial!

The current Subversion client for Windows is "svn-1.3.2-setup.exe" which may be downloaded from
http://subversion.tigris.org/
servlets/ProjectDocumentList?folderID=91

Small problem with junit:
Revision 2212 of jruby trunk builds OK but Ant reports
"C:\jruby-trunk\build.xml:191: Could not create task or type of type: junit."

Anonym sa...

Solution to junit problem:

Before running "ant test", copy junit.jar into ant\lib.

Kalyan's Blog sa...

Hey I am facing this problem.
[c:\software\jruby-trunk]svn co svn://svn.codehaus.org/jruby/trunk/jruby %JRUBY_HOME%
svn: Can't connect to host 'svn.codehaus.org': No connection could be made because the target machine actively refused it.

Anonym sa...

Svn server is refusing anonymous access. Please advise