Tuesday, February 21, 2012

Neo4J with Scala Play! 2.0 on Heroku (Part 6) :: Dispatch+Play 2.0

Note

This post is a continuation of this post, which is the fifth part of a blog suite that aims the use of Neo4j and Play2.0 together on Heroku.

Using Neo4J in Play 2.0

In this post, we'll create a Dispatch Handler that handles Neo4J Rest Json calls in Play's Json object.
Having this in our hands, we'll be able to create a really simple service for dispatching Neo4J operations and use them in Play's views.

I've compiled the Play app on github, fork me.

NB: we'll use Dispatch but in case you wish to, you could use the Play's WS feature that might help you a lot (check this out).

Declare Dispatch deps

First of all, we have to update our Play app with the Dispatch dependency. For that, we have to update the sbt configuration file in order to add the related line.
import sbt._
import Keys._
import PlayProject._
object ApplicationBuild extends Build {
val appName = "Play20WithNeo4J"
val appVersion = "1.0"
val sbtIdeaRepo = "sbt-idea-repo" at "http://mpeltonen.github.com/maven/"
val appDependencies = Seq(
// Add your project dependencies here,
"net.databinder" %% "dispatch-http" % "0.8.7" withSources
)
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
resolvers ++= Seq(
sbtIdeaRepo
)
)
}
view raw Build.scala hosted with ❤ by GitHub

Now, that we have updated the project, let update the application by reloading the configuration (if you're already in sbt console) and rebuild our IDEA project.

[Play20WithNeo4J] $ reload
[Play20WithNeo4J] $ idea
view raw gistfile1.sh hosted with ❤ by GitHub

Play's Json Handler

Our goal is to use the Neo4J Rest Api that returns responses Json encoded.
So here, I'll show how we could have such response directly unmarshalled in Json object. In further posts, we'll use such handling feature to get Model instances directly (which is far more interesting).

What is necessary for that is to create a piece of code that is capable to take a subject and convert it to a JsValue. And since we love functional programming, let us have this method taking a continuation that accepts a JsValue.
import dispatch._
import play.api.libs.json._
import play.api.libs.json.Json._
class PlayJsonHandlers(subject: HandlerVerbs) {
/**Process response as JsValue in block */
def >![T](block: (JsValue) => T) = subject >- {
(str) =>
block(parse(str))
}
}
view raw handler.scala hosted with ❤ by GitHub
In this listing, we see that we use the text parser to consume the response payload, then we ask the Play's Json parse function do its job.
Finally, we use the continuation applied to the parsed result.

Neo4J Service

Let's gather some utility urls to retrieve node, relations. In other words, urls for common usages. This service is left simple for further enhancements (next post).
import dispatch._
import models.Model
import dispatch.HttpExecutor
import utils.dispatch.PlayJsonDispatchHttp._
trait Neo4JRestService {
val neoRest = :/("localhost", 7474)
val neoRestBase = neoRest / "db" / "data"
val neoRestNode = neoRestBase / "node"
val neoRestRel = neoRestBase / "relationship"
val neoRestCypher = neoRestBase / "cypher"
def selfRestUriToId(uri: String) = uri.substring(uri.lastIndexOf('/') + 1).toInt
def neoRestNodeIndex(indexName: String) = neoRestBase / "index" / "node" / indexName
def neoRestNodeById(id: Int) = neoRestNode / id.toString
def neoRestRelById(id: Int) = neoRestRel / id.toString
lazy val root:{val id:Int } = Http(neoRestBase <:< Map("Accept" -> "application/json") >! {
jsValue => new {
val id = selfRestUriToId((jsValue \ "reference_node").as[String])
}
})
}
We see that most functions are there to create urls based on ids, but there is also the root one that directly fetches the entry node.

A Controller To Rule Them All

For the sake of this basic usage of our Handler with Neo4J, here are some examples of such requests. (full controller here).
object Neo4J extends Controller {
object neo extends Neo4JRestService
def createNodeWithProperties = Action {
val props = toJson(Map("prop1" -> "value1", "prop2" -> "value2"))
val (node, data: JsObject) = Http(
(neo.neoRestNode <<(stringify(props), "application/json"))
<:< Map("Accept" -> "application/json")
>! {
jsValue =>
((jsValue \ "self").as[String], (jsValue \ "data").as[JsObject])
})
Ok("" + node + " -- " + data.toString)
}
def relationship(id: Int) = Action {
val (rel, start, end) = Http(neo.neoRestRelById(id) <:< Map("Accept" -> "application/json") >! {
jsValue =>
((jsValue \ "self").as[String],
(jsValue \ "start").as[String],
(jsValue \ "end").as[String])
})
Ok("" + start + " - [" + rel + "] - " + end)
}
}
As we can see, all we had to do is to create the correct url by using id, or Neo4J path conventions, then using the Handler operator ( >! ... how it's Play, no ?!), we have the facility to use directly JsValue instance to consume the result.
Okay, it's repetitive and the Json traversal is not shared. Let's us put this aside until the next post.
And before going ahead, I've created a pretty simple and naive view and url mapping. So check the sources on github, play it and tests the /rest et al. urls.

Next post: Enhance the handler and service to manage Domain Object.

No comments:

Post a Comment