Last active
October 10, 2016 06:47
-
-
Save nafg/3cd1ee36e3cce6f45db185cfc3a054d1 to your computer and use it in GitHub Desktop.
Aggregates
This file contains hidden or 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
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)) | |
} | |
} | |
} | |
} |
This file contains hidden or 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
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: _*) | |
} | |
} |
This file contains hidden or 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
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) | |
} | |
} |
This file contains hidden or 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
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