Skip to content

Instantly share code, notes, and snippets.

@andypetrella
Created February 24, 2012 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andypetrella/1903936 to your computer and use it in GitHub Desktop.
Save andypetrella/1903936 to your computer and use it in GitHub Desktop.
Neo4J Play and DAO
//A trait defining some high level operation on graph, hasn't been cleaned but illustrates well what it might be meant for
trait GraphService[Node] {
//entry point of the graph
def root: Node
//get a Node based on its id
def getNode[T <: Node](id: Int): Option[T]
//return all nodes in the graph as a list
def allNodes[T <: Node]: List[T]
//get the target of all relations that a node holds
def relationTargets[T <: Node](start: Node, rel: String): List[Node]
//save the given node in the graph
def saveNode[T <: Node](t: T): T
//index a node
def indexNode[T <: Node](model: T, indexName: String, key: String, value: String)
//create a relationship between two nodes
def createRelationship(start: Node, rel: String, end: Node)
}
abstract class Model[A <: Model[A]] { // weird construction Mmmh... but it's very useful to have Model knowing who is currently extending it.
val id:Int;
//Now save can take two implicits
// m which is the class manifest of A, where A will be User for instance (see below)
// f which is the json formatter of the concrete class (again User discussed before)
def save(implicit m:ClassManifest[A], f:Format[A]):A = graph.saveNode[A](this.asInstanceOf[A])
}
case class User(id: Int, firstName: String) extends Model[User] { //here we see that we use the F-Bounded to define User as a Model of type User...)
//HERE IS THE GAIN : we don't have to redefine save, because of the extension.
}
class ModelHandlers(subject: HandlerVerbs) {
//Process response as Model Instance in block.
// Beware of the filter function that is meant to keep only the relevant data for our Model
def >^>[M <: Model[_], T](block: (M) => T)(implicit fmt: Format[M], filter: (JsValue) => JsValue = (j: JsValue) => j) = new PlayJsonHandlers(subject) >! {
(jsValue) =>
block(fromJson[M](filter(jsValue))) // Here we use the formatter in the implicits and the filter to match its needs.
}
// a handler for several result at once -- the filter method is meant to take a JsValue and to return an Iterable of JsValue
def >^*>[M <: Model[_], T](block: (Iterable[M]) => T)(implicit fmt: Format[M], filter: (JsValue) => Iterable[JsValue] = (j: JsValue) => Seq(j)) = new PlayJsonHandlers(subject) >! {
(jsValue) =>
block(filter(jsValue).map(fromJson[M](_)))
}
}
object Model {
// a mutable map that holds the look up table between class manifest and relation kind (String)
val models:mutable.Map[String, ClassManifest[_ <: Model[_]]] = new mutable.HashMap[String, ClassManifest[_ <: Model[_]]]()
//this helps the registration
// in Play, we can use the GlobalSetttings hook to register definitions (as Hibernate does f.i.)
def register[T <: Model[_]](kind:String)(implicit m:ClassManifest[T]) {
models.put(kind, m)
}
//determines the relation kind of a class
def kindOf[T <: Model[_]] (implicit m:ClassManifest[T]):String = models.find(_._2.equals(m)).get._1
}
//Model definition
abstract class Model[A <: Model[A]] {
val id:Int;
}
//Concrete class
case class User(id: Int, firstName: String) extends Model[User] {
}
//companion object that defines the Format in the implicit scope
object User {
implicit object UserFormat extends Format[User] {
def reads(json: JsValue): User = User(
(json \ "id").asOpt[Int].getOrElse(null.asInstanceOf[Int]),
(json \ "firstName").as[String]
)
def writes(u: User): JsValue =
JsObject(List(
"_class_" -> JsString(User.getClass.getName),
"firstName" -> JsString(u.firstName)
) ::: (if (u.id != null.asInstanceOf[Int]) {
List("id" -> JsNumber(u.id))
} else {
Nil
}))
}
}
trait Neo4JRestService extends GraphService[Model[_]] {
def saveNode[T <: Model[_]](t: T): T = {
//call the Neo4J Rest end point with the model instance formatted in Json
// (A) ==> needs a Json Formatter for the Model instance
//recover the id from the self url
// ==> ok
//set the 'id' property
// ==> ok
//link the instance to the entry node
// (B) ==> needs a way to associate a Class to a relation kind
//retrieve the resulting instance from Neo4J as Json unmarshalled in a related Model instance
// (C) ==> needs a Unformatter for the Model instance
//return the instance
}
}
def saveNode[T <: Model[_]](t: T)(implicit m: ClassManifest[T], f: Format[T]): T = {
////////uses the Format for outputting the model instance to Json////////
val (id: Int, property: String) = Http(
(neoRestNode <<(stringify(toJson(t)), "application/json"))
<:< Map("Accept" -> "application/json")
>! {
jsValue =>
val id: Int = selfRestUriToId((jsValue \ "self").as[String])
(id, (jsValue \ "property").as[String])
}
)
//update the id property
Http(
(url(property.replace("{key}", "id")) <<(id.toString, "application/json") PUT)
<:< Map("Accept" -> "application/json") >| //no content
)
//////check below///////////
val model = getNode[T](id).get
//create the rel for the kind
linkToRoot(Model.kindOf[T], model) //////// get the relation kind to create //////////
model
}
//WARN :: the name conforms is mandatory to avoid conflicts with Predef.conforms for implicits
// see https://issues.scala-lang.org/browse/SI-2811
// This filter will simply keep the data property of the Neo4J response
implicit def conforms: (JsValue) => JsValue = {
(_: JsValue) \ "data"
}
def getNode[T <: Model[_]](id: Int)(implicit m: ClassManifest[T], f: Format[T]): Option[T] = {
////////// see how we simply get the Model instance by using our handler and the implicit filter (conforms) defined above ////////////
Http(neoRestNodeById(id) <:< Map("Accept" -> "application/json") >^> (Some(_: T)))
}
class UserSpec extends Specification {
var userId:Int = 0;
def is =
"Persist User" ^ {
"is save with an id" ! {
running(FakeApplication()) {
val user:User = User(null.asInstanceOf[Int], "I'm you")
val saved: User = user.save
userId = saved.id
saved.id must beGreaterThanOrEqualTo(0)
}
} ^
"can e retrieved easily" ! {
running(FakeApplication()) {
val retrieved = graph.getNode[User](userId)
retrieved must beSome[User]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment