-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
'Stats' example query resource with model + specs
- Loading branch information
Martin Kleppmann
committed
Jun 14, 2009
1 parent
039d782
commit 4a6c074
Showing
3 changed files
with
143 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
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 | ||
} | ||
} |
This file contains 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
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") | ||
} |
This file contains 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
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) | ||
} | ||
} | ||
} | ||
} |