Skip to content

Instantly share code, notes, and snippets.

@nafg
Last active October 10, 2016 06:47
Show Gist options
  • Save nafg/3cd1ee36e3cce6f45db185cfc3a054d1 to your computer and use it in GitHub Desktop.
Save nafg/3cd1ee36e3cce6f45db185cfc3a054d1 to your computer and use it in GitHub Desktop.
Aggregates
package chesednow.model.common
import scala.concurrent.ExecutionContext
import slick.additions.entity._
import slick.ast.BaseTypedType
import chesednow.shared.common.{ChildItems, GenAggregates, RootAggregates}
import lrbcol.model.DBComponent
import lrbcol.model.SlickDriver.api._
import monocle.Lens
trait AggregateStorageComponent {
this: DBComponent =>
abstract class RootAggregateStorage[K, A, T <: EntityTable[K, A], G <: RootAggregates[K, A]](aggregates: G,
val tableQuery: EntTableQuery[K, A, T])
(implicit parentLookupType: BaseTypedType[Lookup[K, A]])
extends GenAggregateStorage[K, A, G](aggregates) {
type ParentTable = T
type SaveParam = Unit
override protected def saveNewParent(aggregate: G#New, ignore: Unit)(implicit executionContext: ExecutionContext) =
tableQuery.insert(aggregate.parent)
def save(aggregate: G#Aggregate)(implicit executionContext: ExecutionContext): DBIO[G#Existing] =
save(aggregate, ())
}
abstract class GenAggregateStorage[K, A, G <: GenAggregates[KeyedEntity[K, A], Entity[K, A], Lookup[K, A], _]](aggregates: G)(implicit parentLookupType: BaseTypedType[Lookup[K, A]]) {
type ParentTable <: EntityTable[K, A]
type SaveParam
def tableQuery: EntTableQuery[K, A, ParentTable]
class ChildItemsSaver[ChildKey, ChildValue, ChildTable <: EntityTable[ChildKey, ChildValue]](tableQuery: EntTableQuery[ChildKey, ChildValue, ChildTable],
loadQuery: Lookup[K, A] => Query[ChildTable, KeyedEntity[ChildKey, ChildValue], Seq])
(implicit shape: Shape[FlatShapeLevel, ChildTable#MappedProj[_, _, ChildValue], ChildValue, ChildTable#MappedProj[_, _, ChildValue]],
lookupType: BaseTypedType[Lookup[ChildKey, ChildValue]]) {
def doInserts(parentedInserts: Seq[ChildValue]): DBIO[Seq[KeyedEntity[ChildKey, ChildValue]]] =
tableQuery.map(_.mapping).returning(tableQuery) ++= parentedInserts
def doUpdate(modifiedEntity: ModifiedEntity[ChildKey, ChildValue])(implicit executionContext: ExecutionContext): DBIO[SavedEntity[ChildKey, ChildValue]] =
tableQuery.save(modifiedEntity)
def doDeletes(toDelete: Seq[Lookup[ChildKey, ChildValue]]): DBIO[Int] =
tableQuery.filter(t => t.lookup inSet toDelete).delete
def save[Orphan](childItems: ChildItems.Existing[ChildKey, ChildValue, Orphan])(implicit executionContext: ExecutionContext) = {
val updateAction =
DBIO.sequence(childItems.existing.map {
case saved@SavedEntity(_, _) => DBIO.successful(saved)
case modified@ModifiedEntity(_, _) => doUpdate(modified)
})
for {
inserted <- doInserts(childItems.inserts.map(childItems.parented))
_ <- doDeletes(childItems.deletes)
updated <- updateAction
} yield childItems.reset(inserted ++ updated)
}
}
def childrenStorage: Seq[ChildrenStorageBase]
trait ChildrenStorageBase {
def save(container: G#Existing)(implicit executionContext: ExecutionContext): DBIO[G#Existing]
def load(lookup: Lookup[K, A], container: G#Existing)(implicit executionContext: ExecutionContext): DBIO[G#Existing]
}
class ChildrenStorage[Orphan, ChildKey, ChildValue, ChildTable <: EntityTable[ChildKey, ChildValue]](tableQuery: EntTableQuery[ChildKey, ChildValue, ChildTable],
childrenLens: Lens[G#Existing, ChildItems.Existing[ChildKey, ChildValue, Orphan]])
(parent: Lookup[K, A] => Orphan => ChildValue,
orphan: ChildValue => Orphan,
lookupColumn: ChildTable => Rep[Lookup[K, A]])
(implicit shape: Shape[FlatShapeLevel, ChildTable#MappedProj[_, _, ChildValue], ChildValue, ChildTable#MappedProj[_, _, ChildValue]],
lookupType: BaseTypedType[Lookup[ChildKey, ChildValue]])
extends ChildrenStorageBase {
def save(container: G#Existing)(implicit executionContext: ExecutionContext) = {
val childItems = childrenLens.get(container)
new ChildItemsSaver(tableQuery, lookup => tableQuery.filter(t => columnExtensionMethods(lookupColumn(t)) === lookup))
.save(childItems)
.map(childrenLens.set(_)(container))
}
override def load(lookup: Lookup[K, A], container: G#Existing)(implicit executionContext: ExecutionContext) =
tableQuery.filter(lookupColumn(_) === lookup).result
.map { items =>
childrenLens.set(ChildItems.ofExisting(parent(lookup), orphan, items))(container)
}
}
protected def saveNewParent(aggregate: G#New, input: SaveParam)(implicit executionContext: ExecutionContext): DBIO[SavedEntity[K, A]]
protected def saveExistingParent(aggregate: G#Existing)(implicit executionContext: ExecutionContext) =
aggregate.parentEnt match {
case saved@SavedEntity(_, _) => DBIO.successful(saved)
case modified@ModifiedEntity(_, _) => tableQuery.update(modified)
}
def save(aggregate: G#Aggregate, input: SaveParam)(implicit executionContext: ExecutionContext) = {
val savedParent = aggregate.fold(
n => saveNewParent(n, input),
e => saveExistingParent(e)
)
savedParent
.map(ent => aggregate.withKeyedParent(ent))
.flatMap { existing =>
childrenStorage.foldLeft(DBIO.successful(existing): DBIO[G#Existing])(_ flatMap _.save)
}
}
def load(lookup: Lookup[K, A])(implicit executionContext: ExecutionContext) =
tableQuery.filter(_.lookup === lookup).result.head.flatMap { parent =>
childrenStorage.foldLeft(DBIO.successful(aggregates.emptyExisting(parent)): DBIO[G#Existing]) { (acc, s) =>
acc.flatMap(e => s.load(lookup, e))
}
}
}
}
package chesednow.shared.common
import scala.language.higherKinds
import slick.additions.entity.{KeyedEntity, Lookup}
sealed trait ChildItemsBase {
type Orphan
}
sealed trait ChildItems[O] extends ChildItemsBase {
override type Orphan = O
protected def _inserts: Seq[(ChildItems.Handle, Orphan)]
def inserts: Seq[Orphan] = _inserts.map(_._2)
}
object ChildItems {
class Handle
sealed trait Type {
type Of[Parent, ChildKey, Child, Orphan] <: ChildItems[Orphan]
}
object Type {
final class New private extends Type {
type Of[Parent, ChildKey, Child, Orphan] = ChildItems.New[Parent, ChildKey, Child, Orphan]
}
final class Existing private extends Type {
type Of[Parent, ChildKey, Child, Orphan] = ChildItems.Existing[ChildKey, Child, Orphan]
}
}
final class Of[Parent, ChildKey, Child, Orphan] private {
type New = Type.New#Of[Parent, ChildKey, Child, Orphan]
type Existing = Type.Existing#Of[Parent, ChildKey, Child, Orphan]
}
case class Operable[I <: ChildItemsBase](ops: Operations[I], value: I#Orphan)
def ofNew[Parent, ChildKey, Child, Orphan](parented: (Orphan, Parent) => Child,
orphaned: Child => Orphan): New[Parent, ChildKey, Child, Orphan] =
New(
parented = parented,
orphaned = orphaned,
_inserts = Nil
)
def ofExisting[ChildKey, Child, Orphan](parented: Orphan => Child,
orphaned: Child => Orphan,
existing: Seq[KeyedEntity[ChildKey, Child]]): Existing[ChildKey, Child, Orphan] =
Existing(
parented = parented,
orphaned = orphaned,
_inserts = Nil,
deletes = Nil,
existing = existing.toList
)
case class New[Parent, ChildKey, Child, O] private(parented: (O, Parent) => Child,
orphaned: Child => O,
protected[ChildItems] val _inserts: List[(Handle, O)],
undo: Option[ChildItems[O]] = None)
extends ChildItems[O] {
def withLookup(parent: Parent) = Existing[ChildKey, Child, O](
parented = parented(_: O, parent),
orphaned = orphaned,
_inserts = _inserts,
deletes = Nil,
existing = Nil,
undo = Some(this)
)
}
case class Existing[ChildKey, Child, Orphan] private(parented: Orphan => Child,
orphaned: Child => Orphan,
protected[ChildItems] val _inserts: List[(Handle, Orphan)],
deletes: List[Lookup[ChildKey, Child]],
existing: List[KeyedEntity[ChildKey, Child]],
undo: Option[ChildItems[Orphan]] = None)
extends ChildItems[Orphan] {
def parentedInserts = inserts.map(parented)
def reset(xs: Seq[KeyedEntity[ChildKey, Child]]) =
copy(existing = xs.toList, _inserts = Nil, deletes = Nil, undo = None)
}
trait Operations[I <: ChildItemsBase] {
def update(newValue: I#Orphan): I => I = modify(_ => newValue)
def modify(f: I#Orphan => I#Orphan): I => I
def delete: I => I
}
private class NewInsertedOps[Parent, ChildKey, Child, Orphan](handle: Handle) extends Operations[New[Parent, ChildKey, Child, Orphan]] {
override def modify(f: Orphan => Orphan) = childItems => childItems.copy(
_inserts = childItems._inserts.map {
case (`handle`, bo) => (handle, f(bo))
case other => other
},
undo = Some(childItems)
)
override def delete = childItems =>
childItems.copy(_inserts = childItems._inserts.filter(_._1 ne handle), undo = Some(childItems))
}
private class ExistingInsertedOps[ChildKey, Child, Orphan](handle: Handle)
extends Operations[Existing[ChildKey, Child, Orphan]] {
override def modify(f: Orphan => Orphan) = childItems => childItems.copy(
_inserts = childItems._inserts.map {
case (`handle`, bo) => (handle, f(bo))
case other => other
},
undo = Some(childItems))
override def delete = childItems =>
childItems.copy(_inserts = childItems._inserts.filter(_._1 ne handle), undo = Some(childItems))
}
private class ExistingExistingOps[ChildKey, Child, Orphan](key: ChildKey)
extends Operations[Existing[ChildKey, Child, Orphan]] {
override def modify(f: Orphan => Orphan) = childItems => childItems.copy(
existing = childItems.existing.map {
case ke if ke.key == key =>
ke.map((child: Child) => childItems.parented(f(childItems.orphaned(child))))
case other => other
},
undo = Some(childItems))
override def delete = childItems => {
val (toDelete, others) = childItems.existing.partition(_.key == key)
childItems.copy(
existing = others,
deletes = toDelete ::: childItems.deletes,
undo = Some(childItems)
)
}
}
trait ChildItemOpsImpls[I <: ChildItemsBase] {
def items(i: I): Seq[Operable[I]]
def insert(i: I, orphans: I#Orphan*): I
}
object ChildItemOpsImpls {
implicit def newItems[Parent, ChildKey, Child, Orphan]: ChildItemOpsImpls[New[Parent, ChildKey, Child, Orphan]] =
new ChildItemOpsImpls[New[Parent, ChildKey, Child, Orphan]] {
override def items(childItems: New[Parent, ChildKey, Child, Orphan]) =
childItems._inserts.map { case (handle, bo) =>
Operable[New[Parent, ChildKey, Child, Orphan]](new NewInsertedOps(handle), bo)
}
override def insert(childItems: New[Parent, ChildKey, Child, Orphan], orphans: Orphan*) =
childItems.copy(_inserts = orphans.map(new Handle -> _) ++: childItems._inserts, undo = Some(childItems))
}
implicit def existingItems[ChildKey, Child, Orphan]: ChildItemOpsImpls[Existing[ChildKey, Child, Orphan]] =
new ChildItemOpsImpls[Existing[ChildKey, Child, Orphan]] {
override def items(childItems: Existing[ChildKey, Child, Orphan]) =
childItems._inserts.map { case (handle, bo) =>
Operable[Existing[ChildKey, Child, Orphan]](new ExistingInsertedOps[ChildKey, Child, Orphan](handle), bo)
} ++
childItems.existing.map { case KeyedEntity(key, value) =>
Operable[Existing[ChildKey, Child, Orphan]](new ExistingExistingOps[ChildKey, Child, Orphan](key), childItems.orphaned(value))
}
override def insert(childItems: Existing[ChildKey, Child, Orphan], orphans: Orphan*) =
childItems.copy(_inserts = orphans.map(new Handle -> _) ++: childItems._inserts, undo = Some(childItems))
}
}
implicit class ChildItemOps[I <: ChildItemsBase](self: I) {
def items(implicit impls: ChildItemOpsImpls[I]): Seq[Operable[I]] = impls.items(self)
def insert(orphans: I#Orphan*)(implicit impls: ChildItemOpsImpls[I]): I = impls.insert(self, orphans: _*)
}
}
package chesednow.shared.common
import monocle.{PLens, _}
trait GenAggregates[KeyedParent, ParentEntity, ParentLookup, ParentValue] {
trait Operations[O, T] {
def delete: T
def modify(f: O => O): T
def update(value: O): T = modify(_ => value)
}
case class Operable[O, T](value: O, ops: Operations[O, T])
class ChildLensOps[A <: Aggregate, O, I <: ChildItems[O]](val aggregate: A, val lens: Lens[A, I])
(implicit val impl: ChildItems.ChildItemOpsImpls[I]) {
type Items = I
type Orphan = O
val childItems: I = lens.get(aggregate)
def items: Seq[ChildItems.Operable[Items]] =
ChildItems.ChildItemOps(childItems).items
def values = items.map(_.value)
def operables = items.map {
case ChildItems.Operable(ops, value) =>
val aops = new Operations[O, A] {
override def delete = lens.modify(ops.delete)(aggregate)
override def modify(f: O => O) = lens.modify(ops.modify(f))(aggregate)
}
Operable(value, aops)
}
def insert(value: O): A = lens.set(childItems.insert(value))(aggregate)
}
class ChildLenses[K, C, O](val newLens: Lens[New, ChildItems.New[ParentLookup, K, C, O]],
val existingLens: Lens[Existing, ChildItems.Existing[K, C, O]]) {
def apply[A <: Aggregate](a: A)(implicit sel: SelectLensOps[A]): ChildLensOps[A, O, A#ChildItemType#Of[ParentLookup, K, C, O]] =
sel(a, this)
}
def newParentLens: Lens[New, ParentValue]
def existingParentEntLens: Lens[Existing, KeyedParent]
def existingParentLens: PLens[Existing, Existing, ParentValue, ParentValue]
def emptyExisting(parent: KeyedParent): Existing
trait Aggregate {
type ChildItemType <: ChildItems.Type
type Self <: Aggregate
def parent: ParentValue
def withKeyedParent(keyedEntity: KeyedParent): Existing
def fold[A](n: New => A, e: Existing => A): A
}
trait NewBase extends Aggregate {
this: New =>
override type ChildItemType = ChildItems.Type.New
override type Self = New
override def fold[A](n: New => A, e: Existing => A) = n(this)
}
trait ExistingBase extends Aggregate {
this: Existing =>
override type ChildItemType = ChildItems.Type.Existing
override type Self = Existing
def parentEnt: KeyedParent
override def withKeyedParent(keyedEntity: KeyedParent) = existingParentEntLens.set(keyedEntity)(this)
override def fold[A](n: New => A, e: Existing => A) = e(this)
}
type New <: NewBase
type Existing <: ExistingBase
trait SelectLensOps[A <: Aggregate] {
def apply[K, C, O](a: A, lenses: ChildLenses[K, C, O]): ChildLensOps[A, O, A#ChildItemType#Of[ParentLookup, K, C, O]]
}
object SelectLensOps {
type NewItems[K, C, O] = ChildItems.New[ParentLookup, K, C, O]
implicit val n: SelectLensOps[New] = new SelectLensOps[New] {
override def apply[K, C, O](a: New, lenses: ChildLenses[K, C, O]) =
new ChildLensOps[New, O, ChildItems.New[ParentLookup, K, C, O]](a, lenses.newLens)
}
implicit val e: SelectLensOps[Existing] = new SelectLensOps[Existing] {
override def apply[K, C, O](a: Existing, lenses: ChildLenses[K, C, O]) =
new ChildLensOps[Existing, O, ChildItems.Existing[K, C, O]](a, lenses.existingLens)
}
}
trait AggregateOps[A <: Aggregate] {
def parentUpdated(a: A)(f: ParentValue => ParentValue): A
def modify(a: A)(ifNew: New => New, ifExisting: Existing => Existing): A
}
object AggregateOps {
implicit val newOps: AggregateOps[New] = new AggregateOps[New] {
override def parentUpdated(a: New)(f: ParentValue => ParentValue) = newParentLens.modify(f)(a)
override def modify(a: New)(ifNew: New => New, ifExisting: Existing => Existing) = ifNew(a)
}
implicit val existingOps: AggregateOps[Existing] = new AggregateOps[Existing] {
override def parentUpdated(a: Existing)(f: ParentValue => ParentValue) = existingParentLens.modify(f)(a)
override def modify(a: Existing)(ifNew: New => New, ifExisting: Existing => Existing) = ifExisting(a)
}
}
implicit class AggregateExtensionMethods[A <: Aggregate](self: A) {
def childLens[K, C, O](lenses: ChildLenses[K, C, O])
(implicit selectLensOps: SelectLensOps[A]): ChildLensOps[A, O, A#ChildItemType#Of[ParentLookup, K, C, O]] =
selectLensOps(self, lenses)
def parentUpdated(f: ParentValue => ParentValue)(implicit ops: AggregateOps[A]): A = ops.parentUpdated(self)(f)
def modify(ifNew: New => New, ifExisting: Existing => Existing)(implicit ops: AggregateOps[A]): A =
ops.modify(self)(ifNew, ifExisting)
}
}
package chesednow.shared.common
import slick.additions.entity.{Entity, KeyedEntity, KeylessEntity, Lookup}
import monocle.{Lens, PLens}
trait RootAggregates[ParentKey, ParentValue]
extends GenAggregates[KeyedEntity[ParentKey, ParentValue], Entity[ParentKey, ParentValue], Lookup[ParentKey, ParentValue], ParentValue] {
def keyedEntityLens[K, A] = Lens[KeyedEntity[K, A], A](_.value)(pv => _.map(_ => pv))
def existingParentLens: PLens[Existing, Existing, ParentValue, ParentValue] = existingParentEntLens.composeLens(keyedEntityLens)
trait Aggregate extends super.Aggregate {
def parentEnt: Entity[ParentKey, ParentValue]
}
trait NewBase extends Aggregate with super.NewBase {
this: New =>
override def parentEnt = KeylessEntity(parent)
}
trait ExistingBase extends Aggregate with super.ExistingBase {
this: Existing =>
override def parent = parentEnt.value
}
type New <: NewBase
type Existing <: ExistingBase
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment