Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrading util library and improving docs. #3

Merged
merged 1 commit into from
Nov 22, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# reactiveneo [![Build Status](https://travis-ci.org/websudos/reactiveneo.svg?branch=develop)](https://travis-ci.org/websudos/reactiveneo)

Reactive type-safe Scala DSL for Neo4j

<a id="contributing">Contributing to ReactiveNeo</a>
==============================================
<a href="#table-of-contents">Back to top</a>

Contributions are most welcome!

1. If you don't have direct write access(e.g. you are not from Websudos), fork the repository first.
2. Create a feature branch where all the changes will be made.
3. Do your awesome stuff!
4. Create a release branch according to the GitFlow guidelines.
5. Create a Pull Request from that release branch.
6. Wait for us to merge!(P.S.: We like to think we're really quick at that)


<a id="git-flow">Using GitFlow</a>
==================================
<a href="#table-of-contents">Back to top</a>

To contribute, simply submit a "Pull request" via GitHub.

We use GitFlow as a branching model and SemVer for versioning.

- When you submit a "Pull request" we require all changes to be squashed.
- We never merge more than one commit at a time. All the n commits on your feature branch must be squashed.
- We won't look at the pull request until Travis CI says the tests pass, make sure tests go well.

<a id="style-guidelines">Scala Style Guidelines</a>
===================================================
<a href="#table-of-contents">Back to top</a>

In spirit, we follow the [Twitter Scala Style Guidelines](http://twitter.github.io/effectivescala/).
We will reject your pull request if it doesn't meet code standards, but we'll happily give you a hand to get it right. Morpheus is even using ScalaStyle to
build, which means your build will also fail if your code doesn't comply with the style rules.

Some of the things that will make us seriously frown:

- Blocking when you don't have to. It just makes our eyes hurt when we see useless blocking.
- Testing should be thread safe and fully async, use ```ParallelTestExecution``` if you want to show off.
- Writing tests should use the pre-existing tools.
- Use the common patterns you already see here, we've done a lot of work to make it easy.
- Don't randomly import stuff. We are very big on alphabetized clean imports.
- Morpheus uses ScalaStyle during Travis CI runs to guarantee you are complying with our guidelines. Since breaking the rules will result in a failed build,
please take the time to read through the guidelines beforehand.


76 changes: 72 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# reactiveneo
# reactiveneo [![Build Status](https://travis-ci.org/websudos/reactiveneo.svg?branch=develop)](https://travis-ci.org/websudos/reactiveneo)

Reactive type-safe Scala DSL for Neo4j


# Table of contents

<ol>
<li><a href="#graph-modelling">Graph modelling</a></li>
<li><a href="#nodes">Nodes</a></li>
<li><a href="#relationships">Relationships</a></li>
<li><a href="#indexes">Indexes</a></li>
<li><a href="#querying">Querying</a></li>
</ol>

Reactive typesafe Scala DSL for Neo4j

The library enforces strong type checks that imposes some restrictions on query format. Every node and relationship
used in the query needs to be defined and named.
Expand All @@ -15,9 +27,13 @@ MATCH (wallstreet:Movie { title:'Wall Street' })<-[r:ACTED_IN]-(actor:Actor)
RETURN r
```



# Graph modelling
<a href="#table-of-contents">Back to top</a>

## Nodes
<a href="#table-of-contents">Back to top</a>

Domain model class
```
Expand All @@ -42,22 +58,74 @@ class PersonNode extends Node[PersonNode, Person] {
```

## Relationships
<a href="#table-of-contents">Back to top</a>

Reactiveneo relationship definition
```
import com.websudos.reactiveneo.dsl._

class PersonRelation extends Relationship[PersonRelation, Person] {

object name extends StringAttribute with Index

object age extends IntegerAttribute

def fromNode(data: QueryRecord): Person = {
Person(name[String](data), age[Int](data))
}

}
```

## Indexes
<a href="#table-of-contents">Back to top</a>



# Querying
<a href="#table-of-contents">Back to top</a>

In this example all nodes of Person type are returned.
```
scala> val personNodes = matches[Person].return(p => p).execute
scala> val personNodes = Person.returns(p => p).execute
personNodes: Future[Seq[Person]]
```

You can also query for specific attributes of a node.
```
scala> val personNames = matches[Person].return(p => p.name).execute
scala> val personNames = Person.returns(p => p.name).execute
personNames: Future[Seq[String]]
```

A query that involves attributes matching.
```
scala> val personNodes = Person( p => p.name := "Tom" ).returns(p => p).execute
personNodes: Future[Seq[Person]]
```

Query for a person that has a relationship to another person
```
scala> val personNodes = Person.relatedTo[Person].returns(p => p).execute
personNodes: Future[Seq[Person]]
```

Query for a person that has a relationship to another person with given name
```
scala> val personNodes = Person.relatedTo(Person(p => p.name := "James").returns(p => p).execute
personNodes: Future[Seq[Person]]
```


Query for a person that has a relationship to another person
```
scala> val personNodes = Person.relatedTo(WorkRelationship :-> Person).returns((p1,r,p2) => p1).execute
personNodes: Future[Seq[Person]]
```


Query for a person that has a relationship to another person with given name
```
scala> val personNodes = Person.relatedTo(WorkRelationship(r => r.company := "ABC") :-> Person(p => p.name := "John"))
returns((p1,r,p2) => p1).execute
personNodes: Future[Seq[Person]]
```
32 changes: 16 additions & 16 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import org.scalastyle.sbt.ScalastylePlugin

object reactiveneo extends Build {

val newzlyUtilVersion = "0.1.19"
val scalatestVersion = "2.2.0-M1"
val finagleVersion = "6.17.0"
val UtilVersion = "0.4.0"
val ScalatestVersion = "2.2.0-M1"
val FinagleVersion = "6.20.0"
val playVersion = "2.3.4"
val scalazVersion = "7.0.6"
val ScalazVersion = "7.1.0"

val publishUrl = "http://maven.websudos.co.uk"

Expand Down Expand Up @@ -115,9 +115,9 @@ object reactiveneo extends Build {
"com.chuusai" % "shapeless_2.10.4" % "2.0.0",
"com.github.nscala-time" %% "nscala-time" % "1.0.0",
"com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2",
"com.newzly" %% "util-testing" % newzlyUtilVersion % "test",
"org.scalaz" %% "scalaz-scalacheck-binding" % scalazVersion % "test",
"org.scalatest" %% "scalatest" % scalatestVersion % "test, provided",
"com.websudos" %% "util-testing" % UtilVersion % "test",
"org.scalaz" %% "scalaz-scalacheck-binding" % ScalazVersion % "test",
"org.scalatest" %% "scalatest" % ScalatestVersion % "test, provided",
"org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test"
),
fork in Test := true,
Expand Down Expand Up @@ -147,8 +147,8 @@ object reactiveneo extends Build {
libraryDependencies ++= Seq(
"com.chuusai" % "shapeless_2.10.4" % "2.0.0",
"org.scala-lang" % "scala-reflect" % "2.10.4",
"com.twitter" %% "finagle-http" % finagleVersion,
"com.twitter" %% "util-core" % finagleVersion,
"com.twitter" %% "finagle-http" % FinagleVersion,
"com.twitter" %% "util-core" % FinagleVersion,
"joda-time" % "joda-time" % "2.3",
"org.joda" % "joda-convert" % "1.6",
"com.typesafe.play" %% "play-json" % playVersion,
Expand All @@ -165,8 +165,8 @@ object reactiveneo extends Build {
).settings(
name := "reactiveneo-zookeeper",
libraryDependencies ++= Seq(
"com.twitter" %% "finagle-serversets" % finagleVersion,
"com.twitter" %% "finagle-zookeeper" % finagleVersion
"com.twitter" %% "finagle-serversets" % FinagleVersion,
"com.twitter" %% "finagle-zookeeper" % FinagleVersion
)
)

Expand All @@ -177,13 +177,13 @@ object reactiveneo extends Build {
).settings(
name := "reactiveneo-testing",
libraryDependencies ++= Seq(
"com.twitter" %% "util-core" % finagleVersion,
"com.newzly" %% "util-testing" % newzlyUtilVersion,
"org.scalatest" %% "scalatest" % scalatestVersion,
"com.twitter" %% "util-core" % FinagleVersion,
"com.websudos" %% "util-testing" % UtilVersion,
"org.scalatest" %% "scalatest" % ScalatestVersion,
"org.scalacheck" %% "scalacheck" % "1.11.3",
"org.fluttercode.datafactory" % "datafactory" % "0.8",
"com.twitter" %% "finagle-http" % finagleVersion,
"com.twitter" %% "util-core" % finagleVersion
"com.twitter" %% "finagle-http" % FinagleVersion,
"com.twitter" %% "util-core" % FinagleVersion
)
).dependsOn(
reactiveneoZookeeper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ class IntegerAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObje
query[Int](name).get
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ abstract class JsonParser[R] extends ResultParser[R] {

private[this] def singleErrorMessage(error: (JsPath, scala.Seq[ValidationError])) = {
val (path: JsPath, errors: Seq[ValidationError]) = error
val message = errors.foldLeft(errors.head.message)((acc,err) => s"$acc,${err.message}")
val message = errors.foldLeft(errors.head.message)((acc, err) => s"$acc,${err.message}")
s"Errors at $path: $message"
}

private[client] def buildErrorMessage(error: JsError) = {
error.errors.tail.foldLeft(singleErrorMessage(error.errors.head))((acc,err) => s"acc,${singleErrorMessage(err)}")
error.errors.tail.foldLeft(singleErrorMessage(error.errors.head))((acc, err) => s"acc,${singleErrorMessage(err)}")
}

override def parseResult(response: HttpResponse): Try[R] = {
Expand All @@ -69,4 +69,4 @@ abstract class JsonParser[R] extends ResultParser[R] {
*/
class JsonValidationException(msg: String) extends Exception

class InvalidResponseException(msg: String) extends Exception
class InvalidResponseException(msg: String) extends Exception
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package com.websudos.reactiveneo.client
import com.websudos.reactiveneo.dsl.ReturnExpression
import org.jboss.netty.handler.codec.http.HttpMethod

import scala.concurrent.Future

/**
* REST API endpoints definitions.
* @param path Server query path.
Expand All @@ -42,7 +44,7 @@ class ServerCall[RT](endpoint: RestEndpoint, content: Option[String], returnExpr
}


def execute = {
def execute: Future[Seq[RT]] = {
val result = client.makeRequest[Seq[RT]](endpoint.path, endpoint.method)
result
}
Expand All @@ -58,4 +60,4 @@ object ServerCall {
def apply[RT](endpoint: RestEndpoint, returnExpression: ReturnExpression[RT])(implicit client: RestClient) = {
new ServerCall[RT](endpoint, None, returnExpression)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ private[reactiveneo] abstract class GraphObject[Owner <: GraphObject[Owner, Reco
*/
def attributes: List[AbstractAttribute[_]] = _attributes.toList

/**
* Constructs a pattern for the class related to this accompanying object.
*/
def apply(predBuilder: (Owner => Predicate[_])*)
(implicit m: Manifest[Owner]): Pattern[Owner] = {
val obj = m.runtimeClass.newInstance().asInstanceOf[Owner]
val pattern = Pattern(obj, nodeAliases.head, predBuilder.map(pred => pred(obj)): _*)
pattern
}


Lock.synchronized {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
package com.websudos.reactiveneo.dsl

import com.websudos.reactiveneo.client.{ServerCall, SingleTransaction, RestClient}
import com.websudos.reactiveneo.query.{BuiltQuery, CypherKeywords, CypherQueryBuilder}
import com.websudos.reactiveneo.query.{CypherOperators, BuiltQuery, CypherKeywords, CypherQueryBuilder}

import scala.annotation.implicitNotFound
import scala.concurrent.Future
Expand Down Expand Up @@ -79,20 +79,27 @@ private[reactiveneo] class MatchQuery[
def query: String = builtQuery.queryString


@implicitNotFound("You cannot use two where clauses on a single query")
final def relatesTo(go: GraphObject[GO, _])(implicit ev: WB =:= WhereUnbound): MatchQuery[GO, WB, RB, OB, LB, _] = {
new MatchQuery[GO, WB, RB, OB, LB, Any] (
node,
builtQuery,
aliases)
}

@implicitNotFound("You cannot use two where clauses on a single query")
final def where(condition: GO => Criteria[GO])(implicit ev: WB =:= WhereUnbound): MatchQuery[GO, WhereBound, RB, OB, LB, _] = {
new MatchQuery[GO, WhereBound, RB, OB, LB, Any] (
node,
where(builtQuery, condition(node).clause),
builtQuery.appendSpaced(CypherKeywords.RETURN).appendSpaced(CypherOperators.WILDCARD),
aliases)
}

final def returns[URT](ret: GO => ReturnExpression[URT]): MatchQuery[GO, WB, ReturnBound, OB, LB, URT] = {
final def returns[URT](ret: GO => ReturnExpression[URT]): MatchQuery[GO, WB, ReturnBound, OB, LB, URT] = {
new MatchQuery[GO, WB, ReturnBound, OB, LB, URT] (
node,
builtQuery.appendSpaced(CypherKeywords.RETURN).appendSpaced(ret(node).query(aliases)),
aliases,
Some(ret(node)))
builtQuery.appendSpaced(CypherKeywords.RETURN).appendSpaced(aliases.values.mkString(",")),
aliases)
}

@implicitNotFound("You need to add return clause to capture the type of result")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,18 @@ trait MatchQueryImplicits {
MatchQuery.createRootQuery(pattern, nodeAliases.tail)
}

/**
* Conversion that simplifies query building. It allows to build the query directly from a pattern.
* ```
* PersonNode(p=>p.name := "Mark").returns(p=>p)
* ```
* @param p Predicate that forms initial node for the query
* @tparam N Type of start node.
* @return Returns query object.
*/
implicit def patternToQuery[N <: Node[N, _]](p: Pattern[N]): MatchQuery[N, WhereUnbound, ReturnUnbound, OrderUnbound, LimitUnbound, _] = {
MatchQuery.createRootQuery(p, nodeAliases.tail)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private[reactiveneo] case class Predicate[T](
attribute: AbstractAttribute[T], value: T)(implicit formatter: ValueFormatter[T]) {

val clause: BuiltQuery = {
if(value == null)
if (value == null)
throw new IllegalArgumentException("NULL is not allowed value to be used in predicate.")
new BuiltQuery(attribute.name).append(CypherOperators.COLON).append(value)
}
Expand All @@ -64,4 +64,4 @@ object Predicate {
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ package com.websudos.reactiveneo.dsl
*
* User needs to extend this class when defining nodes he/she wants to use in the queries.
*/
abstract class Relationship[Owner <: Relationship[Owner, Record], Record] extends GraphObject[Owner, Record] {
abstract class Relationship[Owner <: Relationship[Owner, Record], Record]
extends GraphObject[Owner, Record] {


}
Loading