Java から ActiveRecord を利用する(不完全)
Javaプログラムから、JRubyを利用してRails 3.0のActiveRecordを使う実験をしていたが、どうも上手くいかなかった。
環境構築
OSは Windows 7 の64bit版。
Maven2のインストール
最新版のMaven2(3.0.1)をインストール。
こんな感じ。
> mvn -v Apache Maven 3.0.1 (r1038046; 2010-11-23 19:58:32+0900) Java version: 1.6.0_23 Java home: C:\Program Files\Java\jdk1.6.0_23\jre Default locale: ja_JP, platform encoding: MS932 OS name: "windows 7" version: "6.1" arch: "amd64" Family: "windows"
JRuby のインストール
JRuby(x64)の最新版をインストール。
1.5.6 を D:/bin/jruby-1.5.6 にインストールした。
こんな感じ。
> jruby -v jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2010-12-03 9cf97c3) (Java HotSpot(TM) 6 4-Bit Server VM 1.6.0_23) [amd64-java]
ActiveRecord-JDBC のgemをインストール
JavaなのでJDBCで。activerecord-jdbc-adapterの最新版(1.1.0)をインストール。
> gem install activerecord-jdbc-adapter
Invalid output formatter とか出るけど気にしない。
あと、今回はSQLiteを使うのでSQLite3用のgemもインストール。
> gem install activerecord-jdbcsqlite3-adapter
実装
適当なディレクトリを作って、その中に以下のファイルを置く。
/pom.xml
Maven2を使うのでpom。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>example</groupId> <artifactId>jruby</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.jruby</groupId> <artifactId>jruby-complete</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency> </dependencies> <repositories> <repository> <id>codehaus</id> <name>Maven Codehaus repository</name> <url>http://repository.codehaus.org/</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
/src/main/java/User.java
ユーザ情報のI/F。実体はJRubyで実装する。
package example; public interface User { String getName(); }
/src/main/java/UserManager.java
ユーザ管理クラスのI/F。実体はJRubyで実装する。
package example; import java.util.List; import org.jruby.RubyObject; public interface UserManager { /* ユーザ一覧取得 */ List<RubyObject> all_users_ro(); /* ユーザ一覧取得 */ List<User> all_users(); /* ユーザー登録 */ User create_user(String name); /* ユーザーテーブル作成 */ void create_table(); }
実験のために、ユーザの一覧取得サービスはUserのリストで受け取るものと、RubyObjectという生のRubyオブジェクトのリストで受け取るものと、2種類用意した。
/src/main/resources/user_manager.rb
ActiveRecord を使ったユーザ管理クラスを作って、そのインスタンスを返すJRubyスクリプト。
require 'rubygems' require 'arjdbc' class User < ActiveRecord::Base include Java::example.User end class UserManager include Java::example.UserManager def initialize ActiveRecord::Base.establish_connection( :adapter => "jdbcsqlite3", :database => "test.db" ) end def create_table ActiveRecord::Schema.define do create_table :users, :force => true do |t| t.column :name, :string end end end def create_user(name) User.create(:name => name) end def all_users_ro User.all end alias :all_users :all_users_ro end UserManager.new
Java側のUserManagerインタフェースで定義したメソッドを実装している。
all_users() と all_users_ro() は、実体は同じ。
/src/main/java/Main.java
メインクラス。
package example; import java.io.File; import java.util.Arrays; import java.util.List; import org.jruby.RubyObject; import org.jruby.embed.PathType; import org.jruby.embed.ScriptingContainer; public class Main { public static void main(String[] args) throws Exception { String classpath = System.getProperty("java.class.path"); List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator)); ScriptingContainer container = new ScriptingContainer(); container.setHomeDirectory("D:/bin/jruby-1.5.6"); container.setLoadPaths(loadPaths); Object userManagerObject = container.runScriptlet(PathType.CLASSPATH, "user_manager.rb"); UserManager userManager = container.getInstance(userManagerObject, UserManager.class); // 一回目だけ userManager.create_table(); // (1) userManager.create_user("John Doe"); // (2) System.out.println("*** RubyObject ***"); for (RubyObject ro : userManager.all_users_ro()) { User user = (User)ro.toJava(User.class); System.out.println(user.getName()); } System.out.println("*** Auto mapping ***"); for (User user : userManager.all_users()) { System.out.println(user.getName()); } } }
実行
pom.xml のあるディレクトリから、以下のように実行する。
> mvn exec:java -Dexec.mainClass=example.Main
まず1回実行するとこんな結果が表示され、テーブルが作られてユーザが1人登録されたことがわかる。
-- create_table(:users, {:force=>true}) -> 0.8940s -> 0 rows *** RubyObject *** John Doe *** Auto mapping *** John Doe
その後 (1) をコメントアウトすると、既存のテーブルに対してユーザが追加登録され、2件のユーザが表示される。
*** RubyObject *** John Doe John Doe *** Auto mapping *** John Doe John Doe
で、ここからが問題なんだけど、さらに (2) をコメントアウトすると、何故か List
*** RubyObject *** John Doe John Doe *** Auto mapping *** Exception in thread "main" java.lang.ClassCastException: org.jruby.RubyObject cannot be cast to example.User at example.Main.main(Main.java:38)
ruby側で User.create を呼び出しているとOK、呼び出していないと ClassCastException というよくわからない状況。RailsにもJRubyにもまだ未熟のため、この最後の問題がどうしても解決できず。