Thursday, September 08, 2011

Haml on Clojure Web App

I've wrote a couple of blog posts about making RubyGems work on JVM languages over RedBridge. Clojure is among them. So far, I could successfully make simple examples with DataMapper and UUID RubyGems. This time, I tackled a Clojure web app. The Rubygems to mix in was Haml.


To create a Clojure web app, I used Leiningen (https://github.com/technomancy/leiningen) and Ring (https://github.com/mmcgrana/ring) like I did when I wrote the blog post, JRuby on Heroku via Clojure.


I installed Leiningen, then followed the instruction:
$ lein new helloworld
$ cd helloworld
$ vi project.clj


My project.clj became below in the end:
(defproject hello-world "0.0.1"
:dependencies
[[org.clojure/clojure "1.2.1"]
[org.clojure/clojure-contrib "1.2.0"]
[org.jruby/jruby-complete "1.6.4"]
[ring/ring-jetty-adapter "0.3.9"]])

There were the choices such that [ring "0.3.9"] or others, but this worked well enough. Then, I ran:
$ lein deps

This downloaded all jar archives with dependencies and put them in lib directory. Leiningen deletes all entries in lib directory and downloaded jar archives when project.clj is updated. So, I kept in mind to stay away from lib directory.


Next, I wrote the Clojure web app. I needed to think about was how to load Haml gem. There were two choices, setting a load path and putting a gem to some directory that was listed in a classpath. The former is easier but needs to be careful not to see L/Ruby GEM_HOME that might be already there. So, I chose the latter. For just in case, I also wrote the former way commented out.


I used clojure.contrib.clsaspath to know what directories are in the classpath. As far as I checked, I could use classes and src directories. I chose src, but perhaps, there's no difference between them. I copied haml-3.1.2 and whole stuff under that direcrory from I installed the gem in Ruby way. The very important thing is the name, "haml-3.1.2/lib" should be renamed to something else. Leiningen or some other tool deletes all files under "lib" directory while deploying the app on Heroku. (This never happened on my local env) So, I changed the name from "haml-3.1.2/lib" to "haml-3.1.2/gem" . I really struggled to figure out this fact.


Usually, we write "require 'rubygems'; require 'haml'" , however, I wrote "require 'rubygems'; require 'haml-3.1.2/gem/haml'" in this case. JRuby loads Ruby code from classpath. On the classpath list, I have "src", and 'haml-3.1.2/gem/haml' is a relative path to "src" . The code is below (https://gist.github.com/1205198):

1 (ns demo.gemstoclojure
2 (:use ring.adapter.jetty)
3 (:use clojure.contrib.io)
4 (:use clojure.contrib.classpath))
5
6 (import '(org.jruby.embed ScriptingContainer LocalContextScope))
7 (def c (ScriptingContainer. LocalContextScope/THREADSAFE))
8
9 (println (classpath))
10 (println (pwd))
11
12 ;; Using $LOAD_PATH to load haml gem
13 ;(def gempath [(str (pwd) "/src/haml-3.1.2/gem")])
14 ;(. c setLoadPaths gempath)
15 ;(. c runScriptlet "require 'rubygems'; require 'haml'")
16
17 ;; Using classpath to load haml gem
18 (. c runScriptlet "require 'rubygems'; require 'haml-3.1.2/gem/haml'")
19
20 (def engineclass (. c runScriptlet "Haml::Engine"))
21 (def template
22 "%html
23 %head
24 %title
25 Hello Clojure!
26 %body
27 %h2
28 Hello Clojure from Haml!")
29 (def engine (. c callMethod engineclass "new" template Object))
30
31 (defn app [req]
32 {:status 200
33 :headers {"Content-Type" "text/html"}
34 :body (. c callMethod engine "render" String)})
35
36 (defn -main []
37 (let [port (Integer/parseInt (System/getenv "PORT"))]
38 (run-jetty app {:port port})))

The code above:

  • instantiates ScriptingContainer (line 6-7) (RedBridge!)

  • loads haml gem (line 18)

  • gets Haml::Engine class (line 20)

  • writes haml template (line 21-28)

  • instantiates Haml::Engine with template (line 29)

  • renders haml template (line 34)




Then, I wrote Procfile:

web: lein run -m demo.gemstoclojure



At last, I deployed this app on Heroku, and saw the result:

$ curl http://freezing-autumn-54.herokuapp.com
<html>
<head>
<title>
Hello Clojure!
</title>
</head>
<body>
<h2>
Hello Clojure from Haml!
</h2>
</body>
</html>



Yikes! RubyGems worked on the Clojure web app.

1 comment:

Unknown said...

Awesome post! The only issue I had was initializing the "Haml::Engine":

(ns blog.core
(:require [ring.adapter.jetty :as jetty]
[clojure.contrib.io :as io]
[clojure.contrib.classpath :as cp])
(:import [org.jruby.embed ScriptingContainer LocalContextScope]))

(def scripting-container (ScriptingContainer. LocalContextScope/THREADSAFE))

(defn execute-rb [rb-string]
(. scripting-container runScriptlet rb-string))

(defn gem-require [gem]
(execute-rb (str "require '" gem "';")))

(defn gem-require-block []
(gem-require "rubygems")
(gem-require "haml-3.1.3/gem/haml"))

(def haml-engine (execute-rb "Haml::Engine"))

;;;;;;;;;;;;;;;;;;;;;;;
;; The output is:
;;;;;;;;;;;;;;;;;;;;;;;

(NameError) uninitialized constant Haml
[Thrown class org.jruby.embed.EvalFailedException]