Last active
September 21, 2017 23:42
-
-
Save dmitriyvolk/a6664ec57e1295953eaf272adddc650f to your computer and use it in GitHub Desktop.
An experiment around sorting various entities
This file contains 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
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