Java vuelve a ser cool: accediendo a ActiveRecord desde Lisp
No deja de ser curioso el título de este post: java-lisp-ruby todos juntos en una sola línea. La magia que hace todo esto posible es la máquina virtual de Java, en concreto la versión 6 con un poco del futuro código de la versión 7 metido en un jar.
Java es el VHS de los lenguajes, no es el mejor pero al final está en todos los hogares. Y su secreto está en la plataforma sobre la que se ejecuta la máquina virtual JVM, una plataforma Open Source, muy optimizada por largos años de desarrollo y tan omnipresente como el mismo Java. SUN es consciente de los problemas de edad que empieza a tener Java y está intentando combatirlos en dos frentes diferentes, por un lado, rejuveneciendo Java con características más dinámicas y potentes (hace poco se anunción que Java7 soportará funciones lambda aunque esta no es precisamente una caracteristica nueva en otros lenguajes), por otro lado, esta impulsando el que lenguajes de programación más modernos y dinámicos se puedan ejecutar sobre la JVM y puedan acceder a la inmensa cantidad de bibliotecas disponibles actualmente en Java. La especificación donde se está trabajando con este fin es la JSR232 (https://scripting.dev.java.net/) y parece que la cosa está funcionando, actualmente ya se ejecutan unos 200 lenguajes sobre la JVM (ruby, python, groovy, haskell, javascript...).
Os quiero enseñar un ejemplo práctico de qué posibilidades abre lo anterior: un poco de código que permite acceder a una base de datos mediante ActiveRecord, una parte de Ruby on Rails, dese un dialecto de Lisp.
El dialecto de Lisp es Clojure, una revisión de Lisp ideal para una nueva generación de programadores, no necesariamente educados en scheme o common lisp, sino que se están acercando por primera vez al Lenguaje desde py-ruby y similares.
Clojure (http://clojure.org/), quita unos cuantos paréntesis y añade muchas de las cosas buenas de lenguajes como Haskell (estructuras de datos inmutables, listas infintas, list comprehension...) o Erlang (modelo concurrente de actores) ejecutándose sobre la JVM.
Un ejemplo de como se usa esta biblioteca la podemos ver en el código siguiente:
;; iniciamos ruby usando RS232 y JRuby
(start-ruby)
;;nos conectamos a una base de datos usando ActiveRecord
(start-active-record {:adapter (rslit 'jdbcmysql)
:encoding (rslit 'utf8)
:database (rslit 'unkasoftportal_development)
:username (rslit 'root)
:passowrd (rslit "")
:host (rslit 'localhost)})
;;cargamos un modelo ActiveRecord desde un script ruby
(ruby-require '("~/Desarrollo/unkasoft/mobile_portal_2/trunk/app/models/wap_banner"))
;;guardamos una busqueda en la base de datos
(ar-find-all! 'test 'WapBanner ":order => 'link ASC'")
;; ejemplo de como se puede mezclar codigo clojure que accede a objetos ruby
(count '(get-ruby-ref 'test))
;;recuperamos los hash de atributos de modelos active record
(ar-col-attrs 'test)
;; resultado: [#=(org.jruby.RubyHash. {"id" 1, "image_url" "http://foo.com/img.jpg", "line" "esto es un test", "link" "http://google.com"})]
(keys (first (ar-col-attrs 'test)))
;; resultado ("id" "image_url" "line" "link")
(find (first (ar-col-attrs 'test)) "image_url")
;; resultado "http://foo.com/img.jpg"
Las funciones desarrolladas son una pequeña prueba, pero se podrían extender para ofrecer una verdadera biblioteca de acceso a base de datos para Clojure Lisp a través de ruby/ActiveRecord. De ahí a tener un framework web LispOnRails hay un paso.
Lo más curioso de todo esto es que el desarrollo de estas funciones es muy parecido a escribir una macro para Ruby dentro de funciones Lisp (que a su vez se definen como macros Lisp) sustituyendo `,@ por funciones Java.
Os dejo el resto del código, si sois programadores de CommonLisp/Scheme, está página de la web de Clojure (http://clojure.org/lisps) os puede ser útil para ver las diferencias que presenta Clojure frente a estos dialectos de Lisp.
Creo que vamos a empezar a ver cosas muy interesantes dentro de poco en el mundo Java.
(defn ruby-start []
(do
(def rubyEngine (.. (new javax.script.ScriptEngineManager)
(getEngineByName "jruby")))
(def rubyContext (. rubyEngine (getContext)))))
(defn ruby-eval [script]
(. rubyEngine (eval script)))
(defn ruby-require [libs]
(when (not (nil? libs))
(let [ script (.. "require '" (concat (first libs)) (concat "'"))]
(ruby-eval script))
(recur (rest libs))))
(defn rhlit [clj-map]
(let [buffer (.append (new StringBuffer) "{")]
(loop [hash-keys (keys clj-map)]
(if (not (nil? hash-keys))
(do
(let [the-key (first hash-keys)]
(.append buffer (str the-key))
(.append buffer " => ")
(.append buffer (str (last (find clj-map the-key))))
(if (not (nil? (rest hash-keys)))
(.append buffer ",")
(.append buffer "}")))
(recur (rest hash-keys)))
(.toString buffer)))))
(defn rslit [clj-str]
(.toString
(.. (new StringBuffer)
(append "\"")
(append (str clj-str))
(append "\""))))
(defn start-active-record [configuration]
(ruby-require '("rubygems", "active_record"))
(let [script (.. "ActiveRecord::Base.establish_connection("
(concat (rhlit configuration))
(concat ")"))]
(ruby-eval script)))
(defn ruby-ref [name]
(.concat "@clj_ruby_ref_" (str name)))
(defn get-ruby-ref [rref]
(ruby-eval (ruby-ref rref)))
(defn set-ruby-ref! [rref rval]
(ruby-eval (.. (ruby-ref rref)
(concat "= ")
(concat rval))))
(defn ar-find-all! [rr class options]
(let [script (.. (new StringBuffer)
(append (ruby-ref rr))
(append " = ")
(append (str class))
(append ".")
(append "find(:all"))]
(do
(if (not (nil? options))
(.. script
(append ",")
(append options)
(append ")"))
(.. script
(append ")")))
(ruby-eval (.toString script)))))
(defn ar-col-attrs [rr]
(let [arr-size (count (get-ruby-ref rr))]
(if (= arr-size 0)
[]
(loop [i 0
col []]
(if (< i arr-size)
(let [elem (ruby-eval (.. (ruby-ref rr)
(concat "[")
(concat (str i))
(concat "].attributes")))]
(recur (inc i) (conj col elem)))
col)))))
