Monday, July 12, 2010

Clojure uses DataMapper

Clojure is one of the JVM languages. People know this fact well. Also, people know well JRuby is among JVM languages. On the JVM languages, many people have used Java APIs from Clojure or Ruby code. But, we can do more since the JVM languages are able to communicate each other. The communication is done over the API exposed to Java such as JSR223. This means libraries and tools for a particular language are available to use in other JVM languages. For example, Clojure can choose DataMapper gem to interact databases. Usage is not so complicated. How could I make it happen? Here's a small example.


1. Installation

Clojure and JRuby themselves don't need to be installed. Grab the archives and unzip them. JRuby has installers, so you can use those if you like. After setting the path to jruby command, install 3 DataMapper gems. In this example, I used Sqlite3 for my DBMS. This is why I installed the DataMapper adapter for Sqlite3.

jruby -S gem install --no-ri --no-rdoc dm-core dm-sqlite-adapter dm-migrations



2. Run Clojure with the classpath to jruby.jar

My JRuby resides in /Users/yoko/Tools/jruby-1.5.1, so the path to jruby.jar is /Users/yoko/Tools/jruby-1.5.1/lib/jruby.jar. I added one more path. It is a directory for Ruby code for DataMapper, and the path name is /Users/yoko/Works/Samples/datamapper.
Move to the directory where Clojure was expanded, and run Clojure as in below:

java -cp clojure.jar:/Users/yoko/Tools/jruby-1.5.1/lib/jruby.jar:/Users/yoko/Works/Samples/datamapper clojure.main



3. Setup ScriptingContainer

This example uses JRuby's embed core, RedBridge, since it is easier to use compared to JSR223. The first step is to instantiate ScriptingContainer and set it up so that gems can be loaded.

user=> (import '(org.jruby.embed ScriptingContainer PathType))
org.jruby.embed.PathType
user=> (def c (ScriptingContainer.))
#'user/c
user=> (. c setHomeDirectory "/Users/yoko/Tools/jruby-1.5.1")
nil

To verify the setting was correct, I printed out Ruby's $LOAD_PATH.

user=> (. c runScriptlet "p $LOAD_PATH")
["clojure.jar", "/Users/yoko/Tools/jruby-1.5.1/lib/jruby.jar", "/Users/yoko/Works/Samples/datamapper",
"/Users/yoko/Tools/jruby-1.5.1/lib/ruby/site_ruby/1.8",
"/Users/yoko/Tools/jruby-1.5.1/lib/ruby/site_ruby/shared", "/Users/yoko/Tools/jruby-1.5.1/lib/ruby/1.8", "."]
nil

The path seems OK. Let's go forward.


4. Load gems and connect to the database

The second step is to load gems.

user=> (. c runScriptlet "require 'rubygems'; require 'dm-core'; require 'dm-migrations'")
true


Then, setup DataMapper.

user=> (. c runScriptlet "DataMapper.setup(:default, 'sqlite::memory:')")
#<RubyObject #<DataMapper::Adapters::SqliteAdapter:0xa75865>>


Writing Ruby code in method argument is not very nice. Instead, I wrote Ruby code in the *.rb files and evaluated each file loaded from classpath, /Users/yoko/Works/Samples/datamapper.

user=> (. c runScriptlet PathType/CLASSPATH "category_def.rb")
#<RubyObject #<DataMapper::Model::DescendantSet:0x3b9617>>

See category_def.rb below:

# Definition of the Category model
class Category
include DataMapper::Resource

property :id, Serial
property :category, String
property :created_at, DateTime
end

# Migration
DataMapper.auto_migrate!

Since the Category table has been created, I added three new records:

user=> (. c runScriptlet PathType/CLASSPATH "categories.rb")
true

See categories.rb below:

# Create new records
c1 = Category.new
c1.category = 'Kitchen & Food'
c1.save

c2 = Category.new
c2.category = 'Bed & Bath'
c2.save

c3 = Category.new
c3.category = 'Dining'
c3.save

Let's see what were input to Sqlite3.

user=> (. c runScriptlet "p Category.all")
[#<Category @id=1 @category="Kitchen & Food" @created_at=nil>, #<Category
@id=2 @category="Bed & Bath" @created_at=nil>, #<Category @id=3 @category="Dining" @created_at=nil>]
nil



As in this example, DataMapper worked with Clojure! MySQL and PostgreSQL are available to use from Clojure via DataMapper. Not just DataMapper. Other Ruby gems are also friends of Clojure.

No comments: