Skip to content

Instantly share code, notes, and snippets.

@dmitriyvolk
Last active September 21, 2017 23:42
Show Gist options
  • Save dmitriyvolk/a6664ec57e1295953eaf272adddc650f to your computer and use it in GitHub Desktop.
Save dmitriyvolk/a6664ec57e1295953eaf272adddc650f to your computer and use it in GitHub Desktop.
An experiment around sorting various entities
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
sealed trait Direction
case object Ascending extends Direction
case object Descending extends Direction
trait HasOriginal[T] {
def original: T
}
trait OrderBy[Original, Enrichment] {
type Enriched
protected def ordering: Ordering[Enriched]
def toOriginal(enriched: Enriched): Original
def apply(direction: Direction): Ordering[Enriched] = direction match {
case Ascending => ordering
case Descending => ordering.reverse
}
}
trait OrderByEnriched[Original, Enrichment] extends OrderBy[Original, Enrichment] {
override type Enriched = HasOriginal[Original] with Enrichment
override def toOriginal(enriched: Enriched): Original = enriched.original
}
class ByField[Original, Field](resolve: Original => Field)(implicit fieldOrdering: Ordering[Field]) extends OrderBy[Original, Field] {
override type Enriched = Original
override def ordering: Ordering[Enriched] = Ordering.by[Original, Field](resolve)
override def toOriginal(enriched: Enriched): Original = enriched
}
class ByFieldEnriched[Original, Enrichment, Field](resolve: Enrichment => Field)(implicit fieldOrdering: Ordering[Field]) extends OrderByEnriched[Original, Enrichment] {
override protected def ordering: Ordering[Enriched] = Ordering.by[Enriched, Field](resolve)
}
trait Enricher[Original, Enriched] {
def enrich(originals: Seq[Original]): Seq[Enriched]
}
trait Sorter {
def sort[Original, Enrichment](originals: Seq[Original])(orderBy: OrderBy[Original, Enrichment])(direction: Direction)(implicit enricher: Enricher[Original, orderBy.Enriched]): Seq[Original] = {
enricher.enrich(originals).sorted(orderBy.apply(direction)).map(orderBy.toOriginal)
}
}
class SortSpec extends Specification {
"Sorting" should {
"sort runs by project name" in new Fixture {
println(sorter.sort(runs)(new ByProjectName)(Ascending))
ok
}
"sort workspaces by project name" in new Fixture {
println(sorter.sort(workspaces)(new ByProjectName)(Descending))
ok
}
"sort by run name" in new Fixture {
println(sorter.sort(runs)(ByRunName)(Ascending))
ok
}
"sort by workspace name" in new Fixture {
println(sorter.sort(workspaces)(new ByField(_.name))(Ascending))
ok
}
"sort by run number" in new Fixture {
println(sorter.sort(runs)(new ByField(_.runNumber))(Descending))
ok
}
"sort run by starting user id" in new Fixture {
//excercise: make the following compile and run
// println(sorter.sort(runs)(ByStartingUserId()))
ok
}
"sort by mutliple fields" in new Fixture {
println(sorter.sort(workspaces)(ByProjectNameAndDefinitionTitle)(Descending))
ok
}
}
trait Fixture extends Scope {
case class RRun(id: Long, name: String, startingUserId: Long, projectId: Long, runNumber: Int)
case class WWorkspace(id: Long, name: String, url: String, anotherProjectId: Long, workspaceDefinitionId: String)
case class PProject(id: Long, name: String)
case class UUser(id: Long, username: String)
case class WWorkspaceDefinition(id: String, title: String)
class HasRun(val original: RRun) extends HasOriginal[RRun]
trait HasProjectName {
def projectName: String
}
trait HasStartingUsername {
def startingUsername: String
}
trait HasWorkspaceDefinitionTitle {
def definitionTitle: String
}
/**
* This could be applied to any entity as long as there's a defined enricher that would add a project name to that entity.
* @tparam Original
*/
class ByProjectName[Original] extends ByFieldEnriched[Original, HasProjectName, String](_.projectName)
final val ByStartingUsername = new ByFieldEnriched((e: HasStartingUsername) => e.startingUsername)
case object ByRunName extends ByField[RRun, String](_.name)
case object ByProjectNameAndDefinitionTitle extends OrderByEnriched[WWorkspace, HasProjectName with HasWorkspaceDefinitionTitle] {
override protected def ordering: Ordering[Enriched] = Ordering.by((elem: Enriched) => (elem.projectName, elem.definitionTitle))
}
val runs = Seq(
RRun(1, "run-1", 11, 111, 4),
RRun(2, "run-2", 22, 222, 3),
RRun(3, "run-3", 11, 222, 2),
RRun(4, "run-4", 22, 111, 1)
)
val users = Seq(
UUser(11, "user-11"),
UUser(22, "user-22")
)
val projects = Seq(
PProject(111, "project-111"),
PProject(222, "project-222")
)
val workspaces = Seq(
WWorkspace(1111, "workspace-1111", "http://workspace-1111", 222, "jupyter"),
WWorkspace(2222, "workspace-1111", "http://workspace-2222", 111, "jupyter")
)
val jupyter = WWorkspaceDefinition("jupyter", "Jupyter Notebook")
private def project(id: Long) = projects.find(_.id == id)
private def user(id: Long) = users.find(_.id == id)
implicit val addProjectNamesToRuns: Enricher[RRun, HasOriginal[RRun] with HasProjectName] = new Enricher[RRun, HasOriginal[RRun] with HasProjectName] {
override def enrich(originals: Seq[RRun]): Seq[HasOriginal[RRun] with HasProjectName] = {
originals.map { originalRun =>
new HasRun(originalRun) with HasProjectName {
override def projectName = project(originalRun.projectId).map(_.name).getOrElse(throw new RuntimeException("Not found"))
}
}
}
}
implicit val addProjectNameToWorkspaces: Enricher[WWorkspace, HasOriginal[WWorkspace] with HasProjectName] = new Enricher[WWorkspace, HasOriginal[WWorkspace] with HasProjectName] {
override def enrich(workspaces: Seq[WWorkspace]): Seq[HasOriginal[WWorkspace] with HasProjectName] = workspaces.map { originalWorkspace =>
new HasOriginal[WWorkspace] with HasProjectName {
override def original: WWorkspace = originalWorkspace
override def projectName = project(originalWorkspace.anotherProjectId).map(_.name).getOrElse(throw new RuntimeException("Not found"))
}
}
}
type HasWorkspaceWithProjectNameAndDefinitionTitle = HasOriginal[WWorkspace] with HasProjectName with HasWorkspaceDefinitionTitle
implicit val addBothProjectAndWorkspaceDefinition: Enricher[WWorkspace, HasWorkspaceWithProjectNameAndDefinitionTitle] = new Enricher[WWorkspace, HasWorkspaceWithProjectNameAndDefinitionTitle] {
override def enrich(originals: Seq[WWorkspace]) = addProjectNameToWorkspaces.enrich(originals).map { workspace =>
new HasOriginal[WWorkspace] with HasProjectName with HasWorkspaceDefinitionTitle {
override def original = workspace.original
override def projectName = workspace.projectName
override def definitionTitle = jupyter.title
}
}
}
implicit def noEnrichment[T]: Enricher[T, T] = new Enricher[T, T] {
override def enrich(originals: Seq[T]) = originals
}
val sorter = new Sorter {}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment