Skip to content

Instantly share code, notes, and snippets.

@lemonxah
Created August 2, 2016 09:11
Show Gist options
  • Save lemonxah/81175234655c85bec93367a4dfb684b7 to your computer and use it in GitHub Desktop.
Save lemonxah/81175234655c85bec93367a4dfb684b7 to your computer and use it in GitHub Desktop.
trait ModelWithId[A] { def copy(id: Option[String]):A }
trait Model { def deleted: Boolean; def id: Option[String] }
trait DAO[A <: Model, B, C, Q] {
def interpreter(q: Query, empty: Q): Throwable \/ Q
def insert(a: A)(implicit m: Mappable[A, B, C], ev: A ⇒ ModelWithId[A]): A
def insertRaw(b: B)
def list(skip: Int = 0, limit: Int = 0)(implicit m: Mappable[A, B, C]): Vector[A]
def filter(query: Query, skip: Int = 0, limit: Int = 0)(implicit m: Mappable[A, B, C]): Vector[A]
def headOption(query: Query)(implicit m: Mappable[A, B, C]): Option[A]
def listB[β](skip: Int = 0, limit: Int = 0, fields: Option[List[String]] = None)(implicit m: Mappable[β, B, C]): Vector[β]
def filterB[β](query: Query, skip: Int = 0, limit: Int = 0, fields: Option[List[String]] = None)(implicit m: Mappable[β, B, C]): Vector[β]
def headOptionB[β](query: Query, fields: Option[List[String]] = None)(implicit m: Mappable[β, B, C]): Option[β]
def update(a: A, query: Query)(implicit m: Mappable[A, B, C])
def delete(a: A)(implicit m: Mappable[A, B, C])
def delete(query: Query)
def drop(): Unit
// SHORTHAND NOTATION
def -=(a: A)(implicit m: Mappable[A, B, C]): Unit = delete(a)(m)
def +=(a: A)(implicit m: Mappable[A, B, C], ev: A ⇒ ModelWithId[A]): A = insert(a)(m, ev)
//def :=(a: A, query: Query)(implicit m: Mappable[A, B, C]) = update(a, query)(m)
}
trait Writer[A, B] { def write(a: A): B }
trait Cleaner[A, B] { def clean(a: A): B }
trait Value[+A] { def get: A }
case class UnaryValue[+A](a: A) extends Value[A] { override def get: A = a }
case class BinaryValue[+A](a1: A, a2: A) extends Value[(A, A)] { override def get: (A, A) = (a1, a2) }
case class ListValue[+A](a: List[A]) extends Value[List[A]] { override def get: List[A] = a }
case class Field[A](name: String) {
def asc: OrderField = OrderField(this, 1)
def dsc: OrderField = OrderField(this, -1)
def ===(value: A): Query = Equals(this, UnaryValue(value))
def !==(value: A): Query = NotEquals(this, UnaryValue(value))
def <(value: A): Query = LessThan(this, UnaryValue(value))
def >(value: A): Query = GreaterThan(this, UnaryValue(value))
def >=(value: A): Query = GreaterOrEqual(this, UnaryValue(value))
def <=(value: A): Query = LessOrEqual(this, UnaryValue(value))
def =!=(value: List[A]): Query = In(this, ListValue(value))
def =!!=(value: A): Query = ElemMatchList(this, UnaryValue(value))
def <=>(value1: A, value2: A): Query = Between(this, BinaryValue(value1, value2))
}
object FieldExtentions {
implicit class stringField(field: Field[String]) {
def <%>(value: String): Query = Like(field, UnaryValue(value))
def <*>(value: String): Query = Regex(field, UnaryValue(value))
}
}
sealed trait Query { self ⇒
def &&(t: Query): Query = And(self, t)
def ||(t: Query): Query = Or(self, t)
def unary_! : Query = Not(self)
def orderBy(orderings: List[OrderField]): OrderBy = OrderBy(Some(self), orderings)
}
case class OrderField(field: Field[_], order: Int)
sealed trait UnaryOperator extends Query { def query: Query }
sealed trait Operator extends Query { def left: Query; def right: Query }
case class Or(left: Query, right: Query) extends Operator
case class And(left: Query, right: Query) extends Operator
case class Not(query: Query) extends UnaryOperator
case class OrderBy(left: Option[Query], field: List[OrderField]) extends Query
sealed trait Operand extends Query { def field: Field[_]; def value: Value[_] }
case class GreaterOrEqual[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class GreaterThan[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class LessOrEqual[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class LessThan[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class Equals[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class NotEquals[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class In[A](field: Field[A], value: ListValue[A]) extends Operand
case class ElemMatchList[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class Like[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class Regex[A](field: Field[A], value: UnaryValue[A]) extends Operand
case class Between[A](field: Field[A], value: BinaryValue[A]) extends Operand
object QueryExtentions {
implicit def itemlist[T](t: T): List[T] = List(t)
implicit def tuple2list[T](t: (T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple3list[T](t: (T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple4list[T](t: (T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple5list[T](t: (T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple6list[T](t: (T, T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple7list[T](t: (T, T, T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple8list[T](t: (T, T, T, T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple9list[T](t: (T, T, T, T, T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
implicit def tuple10list[T](t: (T, T, T, T, T, T, T, T, T, T)): List[T] = t.productIterator.map(_.asInstanceOf[T]).toList
}
case class MongoQuery(query: DBObject, order: DBObject = MongoDBObject.empty)
object MongoInterpreter {
// Interpreter typeclasses
def opmap: PartialFunction[Operand, String] = {
case _: Equals[_] ⇒ "$eq"
case _: GreaterThan[_] ⇒ "$gt"
case _: GreaterOrEqual[_] ⇒ "$gte"
case _: LessThan[_] ⇒ "$lt"
case _: LessOrEqual[_] ⇒ "$lte"
case _: NotEquals[_] ⇒ "$ne"
case _: In[_] ⇒ "$in"
case _: Regex[_] ⇒ "$regex"
}
implicit val MongoOperandWriter = new Writer[Operand, MongoQuery] {
override def write(o: Operand): MongoQuery = MongoQuery(o match {
case Equals(f, v) ⇒ new BasicDBObject(f.name, v.get)
case Between(f, v) ⇒ new BasicDBObject(f.name, new BasicDBObject("$gte", v.get._1).append("$lte", v.get._2))
case ElemMatchList(f, v) ⇒ new BasicDBObject(f.name, new BasicDBObject("$elemMatch", new BasicDBObject("$in", v.get)))
case Like(f, v) ⇒ new BasicDBObject(f.name, new BasicDBObject("$regex", s".*${v.get}.*"))
case op: Operand ⇒ new BasicDBObject(op.field.name, new BasicDBObject(opmap(op), op.value.get))
})
}
implicit val MongoUnaryOperatorWriter = new Writer[UnaryOperator, MongoQuery] {
override def write(u: UnaryOperator): MongoQuery =
MongoQuery(new BasicDBObject("$not", MongoQueryWriter.write(u.query)))
}
implicit val MongoOperatorWriter = new Writer[Operator, MongoQuery] {
override def write(o: Operator): MongoQuery = MongoQuery((o match
{ case _: Or ⇒ $or; case _: And ⇒ $and})(MongoQueryWriter.write(o.left).query,MongoQueryWriter.write(o.right).query))
}
implicit val MongoOrderByWriter = new Writer[OrderBy, MongoQuery] {
override def write(order: OrderBy): MongoQuery =
MongoQuery(order.left.fold(new MongoDBObject())(q ⇒ MongoQueryWriter.write(q).query),
order.field.foldLeft(new BasicDBObject()) { case (a, o) ⇒ a.append(o.field.name, o.order) })
}
implicit val MongoQueryWriter: Writer[Query, MongoQuery] = new Writer[Query, MongoQuery] {
override def write(a: Query): MongoQuery = a match {
case op: Operand ⇒ MongoOperandWriter.write(op)
case op: Operator ⇒ MongoOperatorWriter.write(op)
case op: UnaryOperator ⇒ MongoUnaryOperatorWriter.write(op)
case op: OrderBy ⇒ MongoOrderByWriter.write(op)
}
}
}
class MongoDAO[A <: Model](coll: MongoCollection) extends DAO[A, DBObject, DBObject, MongoQuery] {
import MongoInterpreter._
val logger = LoggerFactory.getLogger(classOf[MongoDAO[A]])
def interpreter(q: Query, empty: MongoQuery = MongoQuery(MongoDBObject.empty)): Throwable \/ MongoQuery = \/.fromTryCatchNonFatal {
val qq = implicitly[Writer[Query, MongoQuery]].write(q)
logger.trace(s"${Console.BLUE}Query to be run${Console.WHITE}:${Console.GREEN} $qq${Console.RESET}")
qq
}
override def list(skip: Int = 0, limit: Int = 0)(implicit m: Mappable[A, DBObject, DBObject]): Vector[A] = {
coll.find().skip(skip).limit(limit).toVector.map(m.fromDBType)
}
override def filter(query: Query, skip: Int = 0, limit: Int = 0)(implicit m: Mappable[A, DBObject, DBObject]): Vector[A] = {
interpreter(query).fold(_ ⇒ Vector[A](), {q ⇒ coll.find(q.query).sort(q.order).skip(skip).limit(limit).toVector.map(m.fromDBType) })
}
override def headOption(query: Query)(implicit m: Mappable[A, DBObject, DBObject]): Option[A] = {
interpreter(query).fold(_ ⇒ None, q ⇒ coll.find(q.query).sort(q.order).toVector.map(m.fromDBType).headOption)
}
override def insert(a: A)(implicit m: Mappable[A, DBObject, DBObject], ev: A ⇒ ModelWithId[A]): A = {
val aa = a.copy(id = a.id.orElse(UUID.randomUUID().toString.some))
coll.insert(m.toDBType(aa))
aa
}
override def insertRaw(dbo: DBObject): Unit = {
coll.insert(dbo)
}
override def update(a: A, query: Query)(implicit m: Mappable[A, DBObject, DBObject]) {
interpreter(query).map(q ⇒
coll.update(q.query, m.toDBType(a))
)
}
override def delete(a: A)(implicit m: Mappable[A, DBObject, DBObject]) {
coll.findAndRemove(m.toDBType(a))
}
override def delete(query: Query): Unit = {
interpreter(query).map(q ⇒ coll.findAndRemove(q.query))
}
override def drop(): Unit = {
coll.drop()
}
def aggregate[ζ <: AnyRef](fs: DBObject *)(implicit manifest: Manifest[ζ]): Vector[ζ] = {
coll.aggregate(fs.toList, AggregationOptions(AggregationOptions.CURSOR)).map { r =>
grater[ζ].asObject(r)
}.toVector
}
override def listB[β](skip: Int = 0, limit: Int = 0, fields: Option[List[String]] = None)(implicit m: Mappable[β, DBObject, DBObject]): Vector[β] =
coll.find(MongoDBObject.empty, fields.map(_.foldLeft(new BasicDBObject())(_.append(_, 1)))
.getOrElse(MongoDBObject.empty)).skip(skip).limit(limit).toVector.map(m.fromDBType)
override def filterB[β](query: Query, skip: Int = 0, limit: Int = 0, fields: Option[List[String]] = None)
(implicit m: Mappable[β, DBObject, DBObject]): Vector[β] =
interpreter(query).fold(_ ⇒ Vector[β](), { q ⇒
coll.find(q.query, fields.map(_.foldLeft(new BasicDBObject())(_.append(_, 1))).getOrElse(MongoDBObject.empty))
.sort(q.order).skip(skip).limit(limit).toVector.map(m.fromDBType)
})
override def headOptionB[β](query: Query, fields: Option[List[String]] = None)(implicit m: Mappable[β, DBObject, DBObject]): Option[β] =
interpreter(query).fold(_ ⇒ None, q ⇒ coll.find(q.query,
fields.map(_.foldLeft(new BasicDBObject())(_.append(_, 1)))
.getOrElse(MongoDBObject.empty)).sort(q.order).toVector.map(m.fromDBType).headOption)
}
//Patch request
case Success(Patch(fp: FingPatch, user)) ⇒
fp.uuid.fold(
UnaryResponse(none[Fing]).toResponse(Status.BAD_REQUEST, "uuid not specified")
) { fid ⇒
fing.headOption(EFing.uuid === fid && EFing.deleted === false).fold(
UnaryResponse(none[Fing]).toResponse(Status.NOT_FOUND, "Fing not found")
) { ef ⇒
def update_name(name: Option[String]): State[EFing, Unit] = State { s ⇒ (name.fold(s)(un ⇒ s.copy(name = un)), ()) }
def update_description(description: Option[String]): State[EFing, Unit] = State { s ⇒ (description.fold(s)(un ⇒ s.copy(description = un)), ()) }
def update_properties(properties: Option[List[KeyValuePair]]): State[EFing, Unit] = State { s ⇒ (properties.fold(s)(un ⇒ s.copy(properties = un)), ()) }
def update_documentation(documentation: Option[Option[String]]): State[EFing, Unit] = State { s ⇒ (documentation.fold(s)(un ⇒ s.copy(documentation = un)), ()) }
def update_fingSerialNumber(fingSerialNumber: Option[String]): State[EFing, Unit] = State { s ⇒ (fingSerialNumber.fold(s)(un ⇒ s.copy(fingSerialNumber = un)), ()) }
def update_product(product: Option[Option[String]]): State[EFing, Unit] = State { s ⇒ (product.fold(s)(un ⇒ s.copy(product = un)), ()) }
def update_categoryType(categoryType: Option[List[String]]): State[EFing, Unit] = State { s ⇒ (categoryType.fold(s)(un ⇒ s.copy(categoryType = un)), ()) }
def update_location(location: Option[String]): State[EFing, Unit] = State { s ⇒ (location.fold(s)(un ⇒ s.copy(location = un)), ()) }
def update_type(`type`: Option[String]): State[EFing, Unit] = State { s ⇒ (`type`.fold(s)(un ⇒ s.copy(`type` = un)), ()) }
def update_subType(subType: Option[Option[String]]): State[EFing, Unit] = State { s ⇒ (subType.fold(s)(un ⇒ s.copy(subType = un)), ()) }
def update_model(model: Option[Option[String]]): State[EFing, Unit] = State { s ⇒ (model.fold(s)(un ⇒ s.copy(model = un)), ()) }
def updateFing(f: FingPatch): State[EFing, EFing] = for {
_ ← update_name(f.name)
_ ← update_description(f.description)
_ ← update_properties(f.properties)
_ ← update_documentation(f.documentation)
_ ← update_fingSerialNumber(f.fingSerialNumber)
_ ← update_product(f.product)
_ ← update_categoryType(f.categoryType)
_ ← update_location(f.location)
_ ← update_type(f.`type`)
_ ← update_subType(f.subType)
_ ← update_model(f.model)
s ← State.get
} yield s
val uef = updateFing(fp).run(ef)._2
fing.update(uef, EFing.uuid === fid)
UnaryResponse(uef |> fromModel |> some).toResponse(Status.OK, "Fing updated")
}
}(uuid).publish(Topics.Fing.patchResponse)
case class State[S, +A](run: S ⇒ (S, A)) {
def map[B](f: A ⇒ B): State[S, B] = flatMap(a ⇒ State.unit(f(a)))
def flatMap[B](f: A ⇒ State[S, B]): State[S, B] = State { s ⇒
val (s1, a) = run(s)
f(a).run(s1)
}
}
object State {
def unit[S, A](a: A): State[S, A] = State {s ⇒ (s, a)}
def map2[S, A, B, C](sa: State[S, A], sb: State[S, B])(f: (A, B) ⇒ C): State[S, C] = sa.flatMap(a ⇒ sb.map(b ⇒ f(a,b)))
def map3[S, A, B, C, D](sa: State[S, A], sb: State[S, B], sc: State[S,C])(f: (A, B, C) ⇒ D): State[S, D] =
sa.flatMap(a ⇒ sb.flatMap(b ⇒ sc.map(c ⇒ f(a,b,c))))
def sequence[S, A](ls: List[State[S, A]]): State[S, List[A]] = ls.foldRight(unit[S,List[A]](List[A]()))((f,acc) ⇒ map2(f, acc)(_ :: _))
def get[S]: State[S,S] = State { s ⇒ (s,s) }
def gets[S, A](f: S ⇒ A): State[S, A] = State { s: S ⇒ (s, f(s))}
def put[S](s: S): State[S, Unit] = State { _ ⇒ (s, ())}
def modify[S](f: S ⇒ S): State[S, Unit] = State { s ⇒ (f(s), ())}
def state[S, A](a:A): State[S, A] = State { s ⇒ (s, a)}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment