Skip to content

Commit

Permalink
'Stats' example query resource with model + specs
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Kleppmann committed Jun 14, 2009
1 parent 039d782 commit 4a6c074
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/main/scala/com/example/models/Stats.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.models

import java.util.logging.Logger
import org.codehaus.jettison.json.JSONObject
import org.neo4j.api.core._
import com.eptcomputing.neo4j.IteratorConverters

/**
* Example of a read-only model object. It is initialised with a Neo4j node and counts how many
* other nodes are reachable from that node by outgoing "KNOWS" relationships, grouped by length
* of relationship chain.
*
* Example tests for this class are given in <tt>StatsSpec</tt>.
*/
class Stats(startNode: Node) extends IteratorConverters {
private val log = Logger.getLogger(this.getClass.getName)

// Traverse the graph
private val traverser = startNode.traverse(Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH,
ReturnableEvaluator.ALL_BUT_START_NODE, Predicates.KNOWS, Direction.OUTGOING)

private var countByDepth = Map[Int,Int]()

// Count how many times each depth has occurred
for (node <- traverser) {
val depth = traverser.currentPosition.depth
countByDepth += depth -> (countByDepth.getOrElse(depth, 0) + 1)
}

/** Converts this object to JSON. */
def toJSON = {
val json = new JSONObject
for ((key, value) <- countByDepth) json.put("depth_" + key, value)
json
}
}
24 changes: 24 additions & 0 deletions src/main/scala/com/example/restapi/StatsResource.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.restapi

import javax.ws.rs.Path
import org.codehaus.jettison.json.JSONObject
import org.neo4j.api.core.{NeoService, Node}

import com.example.models.Stats

/**
* Example of a read-only resource which performs a query (graph traversal) and returns
* a result object as JSON. This class defines the API, while the <tt>Stats</tt> model
* class implements the actual query.
*/
@Path("/stats")
class StatsResource extends com.eptcomputing.neo4j.rest.NeoResource {

def read(neo: NeoService, node: Node) = new Stats(node).toJSON

def create(neo: NeoService, json: JSONObject) = throw new Exception("not allowed")

def update(neo: NeoService, existing: Node, newValue: JSONObject) = throw new Exception("not allowed")

def delete(neo: NeoService, node: Node) = throw new Exception("not allowed")
}
83 changes: 83 additions & 0 deletions src/test/scala/com/example/models/StatsSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.example.models

import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
import org.junit.runner.RunWith
import com.jteigen.scalatest.JUnit4Runner

import org.codehaus.jettison.json.JSONObject
import org.neo4j.api.core._

import com.eptcomputing.neo4j.{NeoConverters, NeoServer}

/**
* This is an example of how you can write good and expressive tests/specs using
* the scalatest spec framework. This spec tests the <tt>Stats</tt> model
* directly, without going through the REST API. For an example of testing full
* API request/response cycles, see <tt>NeoResourceTest</tt>.
*/
@RunWith(classOf[JUnit4Runner])
class StatsSpec extends Spec with ShouldMatchers with NeoConverters {

import Predicates._

describe("Stats model") {

it("should return an empty object if the node has no relationships") {
NeoServer.exec { neo =>
val node = neo.createNode
new Stats(node).toJSON.length should equal(0)
}
}

it("should return the number of neighbours") {
NeoServer.exec { neo =>
val node = neo.createNode
(1 to 3) foreach { _ => node --| KNOWS --> neo.createNode }
new Stats(node).toJSON.getInt("depth_1") should equal(3)
}
}

it("should not count a node twice") {
NeoServer.exec { neo =>
val start = neo.createNode
val end = neo.createNode
start --| "KNOWS" --> neo.createNode --| "KNOWS" --> end
start --| "KNOWS" --> neo.createNode --| "KNOWS" --> end
val stats = new Stats(start).toJSON
stats.getInt("depth_1") should equal(2)
stats.getInt("depth_2") should equal(1)
}
}

it("should not follow inbound relationships") {
NeoServer.exec { neo =>
val node = neo.createNode
neo.createNode --| KNOWS --> node --| KNOWS --> neo.createNode
new Stats(node).toJSON.getInt("depth_1") should equal(1)
}
}

it("should not follow relationships of a different type") {
NeoServer.exec { neo =>
val node = neo.createNode
node --| "KNOWS" --> neo.createNode
node --| "LIKES" --> neo.createNode
new Stats(node).toJSON.getInt("depth_1") should equal(1)
}
}

it("should count only the shortest path to each reachable node") {
NeoServer.exec { neo =>
val start = neo.createNode
val reachByTwoPaths = neo.createNode
start --| KNOWS --> neo.createNode --| KNOWS --> reachByTwoPaths
start --| KNOWS --> neo.createNode --| KNOWS --> neo.createNode --| KNOWS --> reachByTwoPaths
val stats = new Stats(start).toJSON
stats.getInt("depth_1") should equal(2)
stats.getInt("depth_2") should equal(2)
stats.has("depth_3") should equal(false)
}
}
}
}

0 comments on commit 4a6c074

Please sign in to comment.