Created
February 24, 2012 21:40
-
-
Save andypetrella/1903936 to your computer and use it in GitHub Desktop.
Neo4J Play and DAO
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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](_))) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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 | |
})) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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