Making today worse so tomorrow seems better.
JRuby Service in Rails
I have two worlds that need to collide. Rails solves the decade long problem of abusive Java web stacks and Java provides mature codebase to the Rails paradigm. JRuby has brought me the spark, now if I can only get the fire burning.
The problem: I need a Spring context to startup with Rails and use the config/database.yml.
The solution: A JRuby Service.
I want to expose tons of existing Java code and libs into Rails, but do not want to deal with a segmented world. Everything should be configured and started from one place, this is were a JRuby Service helps. The JRuby runtime will automatically load a class implementing a JRuby service interface (such as org.jruby.runtime.load.BasicLibraryService) when it is packaged in a specifically named jar at a specifically named placed.
Originally I was jumping though hoops trying to call static singletons, but ended up scratching my head how to get access to the Rails runtime. After finding hints from Ola Bini, with some searching and source diving, I found the javadoc for org.jruby.runtime.load.LoadService has the key. (An aside, it would be nice if the JRuby folks published the JavaDocs, even if it changed with every release).
Except from LoadService.java JavaDoc:
bq. How to make a class that can get required by JRuby
First, decide on what name should be used to require the extension. In this purely hypothetical example, this name will be ‘active_record/connection_adapters/jdbc_adapter’. Then create the class name for this require-name, by looking at the guidelines above. Our class should be named active_record.connection_adapters.JdbcAdapterService, and implement one of the library-interfaces. The easiest one is BasicLibraryService, where you define the basicLoad-method, which will get called when your library should be loaded.
The next step is to either put your compiled class on JRuby’s classpath, or package the class/es inside a jar-file. To package into a jar-file, we first create the file, then rename it to jdbc_adapter.jar. Then we put this jar-file in the directory active_record/connection_adapters somewhere in JRuby’s load path.
The short and skinny of this? The package defines the directory and the class name determines the jar name. The package period separators are converted to slashes, so the package com.slackworks would be the directory com/slackworks. For brevity, this example uses the simple package slackworks, for a directory of slackworks. The class name, sans Service and converted from CamelCase to under scores determines the jar name. So the RailsSpringService class translates to rails_spring.jar
Building a JRuby Service
Step 0: Have a working JRuby setup.
Already done the JRuby installation dance with a rails instance with JDBC for MySQL to monkey with. All of the following source can be pulled from git://sprocket.slackworks.com/srv/git/samples.git
Step 1: Implementing BasicLibraryService.
For org.jruby.runtime.load.BasicLibraryService, this is fairly straight forward. All you need is to implement the basicLoad method. In addition, the org.jruby.Ruby has a static ThreadLocal reference for the Ruby runtime using methods getCurrentInstance and setCurrentInstance. This will be used as a place for the beans being created in the Spring Context to access the Ruby runtime. Just be careful of the ThreadLocal restrictions in multi-threaded environments. In this scenario of using the Ruby runtime to help build the Spring context, it is fine.
/**
- JRuby Service to load a Spring Context
*/
public class RailsSpringService implements BasicLibraryService {
private static ApplicationContext applicationContext;
- Get access to the Spring Context
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
Complete source at RailsSpringService.java which includes the package declaration that deteremines the directory
Step 2: Setting up the Spring context.
I am not going to get into the knitty gritty about Spring, only showing how Spring can load the rails’ config/database.yml to setup a Datasource. The juicy part is the following:
// Get the RAILS_ENV from the JRuby runtime
Ruby jruby = Ruby.getCurrentInstance();
RubyModule kernel = jruby.getKernel();
String rails_env = kernel.getConstant(“RAILS_ENV”).asJavaString();
This is called by the YamlConfig.java constructor to correctly parse the database.yml and use the configuration for the running
RAILS_ENV.
The DatabaseConfig.java is an extension of YamlConfig specific for handling the database.yml. Lastly, DataSource.java is an extension of Apache Commons DBCP BasicDataSource.java that is constructed using DatabaseConfig.
This allows for a applicationContext.xml of:
<?xml version=“1.0” encoding=“UTF-8”?>
Where the databaseConfig bean loads from the config/database.yml, using the Rails’ RAILS_ENV, which gets passed to the dataSource bean. Now we have a DataSource that is in synch with Rails.
Step 3: Packaging Rails Spring Service.
Using the powers of Maven, the pom.xml has been tweaked so that ‘mvn package’ will create lib/slackworks with the rails_spring.jar and all its dependencies. The lib/slackworks directory need to be copied to the RAILS_ROOT
Step 4: Set the classpath.
JRuby is on the road to having jars automatically be loaded into the JVM, but I do not know of a smart way to automatically load the rails_spring.jar dependencies. To alleviate at, the classpath needs to be manually set to include everything in lib/slackworks, i.e. for Java 6, export CLASSPATH=/rails-app/lib/slackworks/*.
Step 5: Testing with console.
Now that everything is in place, the classpath has been set, time to fire it up for a test run. Simply call from the RAILS_ROOT,
jruby script/console
You will be greet with the normal
Loading development environment
Now load the RailsSpringService, via
require ‘slackworks/rails_spring’
and you will be spammed with logging output as Spring starts up and reads the database.yml
Step 6: Load RailsSpringService in Rails.
In the config/environment.rb, at the bottom at
require ‘slackworks/rails_spring’
Now when Rails starts up, you will be greeted by the same output as Spring starts up, the worlds have collided. Accessing the Spring context from Rails:
include_class ‘slackworks.RailsSpringService’
dataSource = RailsSpringService.getApplicationContext().getBean( ‘dataSource’ )
Next Steps
Instead of ActiveRecord directly creating JDBC connections, create a ActiveRecord adapter that builds a DataSource to dole out connections. The DataSource can be crammed into JNDI, providing a easy access for the Spring Context. This removes the wasteful need of having to start a separate set of database connections for the Spring Context.
Caveat.
The world of JRuby is still a moving target, which should be no suprise to anyone, considering the JRuby team has sprinkled the web and the source with tidbits stating such. Thusly meaning, your mileage may vary.
- About
- Technology, programming, the interwebs and other topics by members of the slackworks community.
- Contributors
- All Posts
-
- Aug 05 2009 Using New Relic add_method_tracer with Class methods
- Jun 30 2009 Estimating Key Collisions (Birthday Problem)
- Jun 29 2009 The Blarg is Back!
- Jul 23 2008 JNDI with Rails
- Jul 06 2008 JRuby Service in Rails
- May 16 2008 Wildcard DNS and Rails
- Jan 30 2008 Sprint Error "911" and the "Download Domain"
- Jan 16 2008 Getting a Good View of Your Couch
- Jan 12 2008 Bring Selenium to the integration party
- Jan 12 2008 Bj Makes Attachment_fu Happy
- Jan 03 2008 Restore Finder magic to a sparsebundle directory
- Dec 18 2007 First Post
0 comments