Skip to content

Commit

Permalink
Merge d8db989 into 5cc36b9
Browse files Browse the repository at this point in the history
  • Loading branch information
philwills committed Apr 27, 2016
2 parents 5cc36b9 + d8db989 commit f3ad509
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 10 deletions.
25 changes: 16 additions & 9 deletions README.md
Expand Up @@ -94,19 +94,26 @@ scala> import com.gu.scanamo.syntax._

scala> val client = LocalDynamoDB.client()
scala> import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType._
scala> val farmersTableResult = LocalDynamoDB.createTable(client)("free")('name -> S)

scala> case class Free(name: String, number: Int)
scala> val farmersTableResult = LocalDynamoDB.createTable(client)("winners")('name -> S)

scala> case class LuckyWinner(name: String, shape: String)
scala> def temptWithGum(child: LuckyWinner): LuckyWinner = child match {
| case LuckyWinner("Violet", _) => LuckyWinner("Violet", "blueberry")
| case winner => winner
| }
scala> val luckyWinners = Table[LuckyWinner]("winners")
scala> val operations = for {
| _ <- ScanamoFree.putAll("free")(List(Free("Monad", 1), Free("Applicative", 2), Free("Love", 3)))
| maybeMonad <- ScanamoFree.get[Free]("free")('name -> "Monad")
| monad = maybeMonad.flatMap(_.toOption).getOrElse(Free("oops", 9))
| _ <- ScanamoFree.put("free")(monad.copy(number = monad.number * 10))
| results <- ScanamoFree.getAll[Free]("free")('name -> List("Monad", "Applicative"))
| _ <- luckyWinners.putAll(
| List(LuckyWinner("Violet", "human"), LuckyWinner("Augustus", "human"), LuckyWinner("Charlie", "human")))
| winners <- luckyWinners.scan()
| winnerList = winners.flatMap(_.toOption).toList
| temptedWinners = winnerList.map(temptWithGum)
| _ <- luckyWinners.putAll(temptedWinners)
| results <- luckyWinners.getAll('name -> List("Charlie", "Violet"))
| } yield results

scala> Scanamo.exec(client)(operations).toList
res1: List[cats.data.Xor[error.DynamoReadError, Free]] = List(Right(Free(Monad,10)), Right(Free(Applicative,2)))
res1: List[cats.data.Xor[error.DynamoReadError, LuckyWinner]] = List(Right(LuckyWinner(Charlie,human)), Right(LuckyWinner(Violet,blueberry)))
```

For more details see the [API docs](http://guardian.github.io/scanamo/latest/api/#com.gu.scanamo.Scanamo$)
Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Expand Up @@ -26,6 +26,8 @@ scalacOptions := Seq(
"-feature",
"-unchecked",
"-language:implicitConversions",
"-language:higherKinds",
"-language:existentials",
"-Xfatal-warnings",
"-Xlint",
"-Yinline-warnings",
Expand Down
126 changes: 126 additions & 0 deletions src/main/scala/com/gu/scanamo/Table.scala
@@ -0,0 +1,126 @@
package com.gu.scanamo

import cats.data.Xor
import com.gu.scanamo.error.DynamoReadError
import com.gu.scanamo.ops.ScanamoOps
import com.gu.scanamo.query.{Query, UniqueKey, UniqueKeys}

/**
* Represents a DynamoDB table that operations can be performed against
*
* {{{
* >>> case class Transport(mode: String, line: String)
* >>> val transport = Table[Transport]("transport")
*
* >>> val client = LocalDynamoDB.client()
* >>> import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType._
*
* >>> LocalDynamoDB.withTable(client)("transport")('mode -> S, 'line -> S) {
* ... import com.gu.scanamo.syntax._
* ... val operations = for {
* ... _ <- transport.putAll(List(
* ... Transport("Underground", "Circle"),
* ... Transport("Underground", "Metropolitan"),
* ... Transport("Underground", "Central")))
* ... results <- transport.query('mode -> "Underground" and ('line beginsWith "C"))
* ... } yield results.toList
* ... Scanamo.exec(client)(operations)
* ... }
* List(Right(Transport(Underground,Central)), Right(Transport(Underground,Circle)))
* }}}
*/
case class Table[V: DynamoFormat](name: String) {
/**
* A secondary index on the table which can be scanned, or queried against
*
* {{{
* >>> case class Transport(mode: String, line: String, colour: String)
* >>> val transport = Table[Transport]("transport")
*
* >>> val client = LocalDynamoDB.client()
* >>> import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType._
* >>> import com.gu.scanamo.syntax._
*
* >>> LocalDynamoDB.withTableWithSecondaryIndex(client)("transport", "colour-index")('mode -> S, 'line -> S)('colour -> S) {
* ... val operations = for {
* ... _ <- transport.putAll(List(
* ... Transport("Underground", "Circle", "Yellow"),
* ... Transport("Underground", "Metropolitan", "Maroon"),
* ... Transport("Underground", "Central", "Red")))
* ... maroonLine <- transport.index("colour-index").query('colour -> "Maroon")
* ... } yield maroonLine.toList
* ... Scanamo.exec(client)(operations)
* ... }
* List(Right(Transport(Underground,Metropolitan,Maroon)))
* }}}
*/
def index(indexName: String) = Index[V](name, indexName)

def put(v: V) = ScanamoFree.put(name)(v)
def putAll(vs: List[V]) = ScanamoFree.putAll(name)(vs)
def get(key: UniqueKey[_]) = ScanamoFree.get[V](name)(key)
def getAll(keys: UniqueKeys[_]) = ScanamoFree.getAll[V](name)(keys)
def delete(key: UniqueKey[_]) = ScanamoFree.delete(name)(key)
}

private[scanamo] case class Index[V: DynamoFormat](tableName: String, indexName: String)

/* typeclass */trait Scannable[T[_], V] {
def scan(t: T[V])(): ScanamoOps[Stream[Xor[DynamoReadError, V]]]
}

object Scannable {
def apply[T[_], V](implicit s: Scannable[T, V]) = s

trait Ops[T[_], V] {
val instance: Scannable[T, V]
def self: T[V]
def scan() = instance.scan(self)()
}

trait ToScannableOps {
implicit def scannableOps[T[_], V](t: T[V])(implicit s: Scannable[T, V]) = new Ops[T, V] {
val instance = s
val self = t
}
}

implicit def tableScannable[V: DynamoFormat] = new Scannable[Table, V] {
override def scan(t: Table[V])(): ScanamoOps[Stream[Xor[DynamoReadError, V]]] =
ScanamoFree.scan[V](t.name)
}
implicit def indexScannable[V: DynamoFormat] = new Scannable[Index, V] {
override def scan(i: Index[V])(): ScanamoOps[Stream[Xor[DynamoReadError, V]]] =
ScanamoFree.scanIndex[V](i.tableName, i.indexName)
}
}

/* typeclass */ trait Queryable[T[_], V] {
def query(t: T[V])(query: Query[_]): ScanamoOps[Stream[Xor[DynamoReadError, V]]]
}

object Queryable {
def apply[T[_], V](implicit s: Queryable[T, V]) = s

trait Ops[T[_], V] {
val instance: Queryable[T, V]
def self: T[V]
def query(query: Query[_]) = instance.query(self)(query)
}

trait ToQueryableOps {
implicit def queryableOps[T[_], V](t: T[V])(implicit s: Queryable[T, V]) = new Ops[T, V] {
val instance = s
val self = t
}
}

implicit def tableQueryable[V: DynamoFormat] = new Queryable[Table, V] {
override def query(t: Table[V])(query: Query[_]): ScanamoOps[Stream[Xor[DynamoReadError, V]]] =
ScanamoFree.query[V](t.name)(query)
}
implicit def indexQueryable[V: DynamoFormat] = new Queryable[Index, V] {
override def query(i: Index[V])(query: Query[_]): ScanamoOps[Stream[Xor[DynamoReadError, V]]] =
ScanamoFree.queryIndex[V](i.tableName, i.indexName)(query)
}
}
2 changes: 1 addition & 1 deletion src/main/scala/com/gu/scanamo/package.scala
Expand Up @@ -4,7 +4,7 @@ import com.gu.scanamo.query._

package object scanamo {

object syntax {
object syntax extends Scannable.ToScannableOps with Queryable.ToQueryableOps {
implicit class SymbolKeyCondition(s: Symbol) {
def <[V: DynamoFormat](v: V) = KeyIs(s, LT, v)
def >[V: DynamoFormat](v: V) = KeyIs(s, GT, v)
Expand Down

0 comments on commit f3ad509

Please sign in to comment.