Skip to content

Commit

Permalink
Merge c3d707b into 5f9dcf8
Browse files Browse the repository at this point in the history
  • Loading branch information
cchantep committed Feb 27, 2021
2 parents 5f9dcf8 + c3d707b commit 415dcd6
Show file tree
Hide file tree
Showing 18 changed files with 815 additions and 24 deletions.
110 changes: 107 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Integrations are available for:
- [ScalaCheck](https://www.scalacheck.org): JVM and ScalaJS
- [Slick](http://slick.lightbend.com/): JVM only
- [Quill](http://getquill.io): JVM and ScalaJS
- [Anorm](https://playframework.github.io/anorm/): JVM only
- [sttp tapir](https://github.com/softwaremill/tapir): JVM and ScalaJS

### Table of Contents
Expand All @@ -58,9 +59,9 @@ Integrations are available for:
13. [Quill integration](#quill)
14. [Cats integration](#cats)
15. [Doobie integration](#doobie)
16. [Benchmarking](#benchmarking)
17. [Publishing](#publishing)

16. [Anorm integration](#anorm)
17. [Benchmarking](#benchmarking)
18. [Publishing](#publishing)

## Quick start

Expand Down Expand Up @@ -1134,6 +1135,7 @@ items.maximumOption // Some(SuperHigh)
If you need instances, but hesitate to mix in the traits demonstrated above, you can get them using the provided methods in `enumeratum.Cats` and `enumeratum.values.Cats` - the second also provides more flexibility than the (opinionated) mix-in trait as it allows to pass a custom type class instance for the value type (methods names are prefixed with `value`).

## Doobie

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-doobie_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-doobie_2.12)

### SBT
Expand Down Expand Up @@ -1259,6 +1261,108 @@ sql"select shirt from clothes"
```
- Note that a type ascription to the `ValueEnumEntry` abstract class (eg. `ShirtSize.Small: ShirtSize`) is required when binding hardcoded `ValueEnumEntry`s

## Anorm

[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-anorm_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-anorm_2.12)

### SBT

To use enumeratum with [Anorm](https://playframework.github.io/anorm/):

```scala
libraryDependencies ++= Seq(
"com.beachape" %% "enumeratum-anorm" % enumeratumAnormVersion
)
```

To use with ScalaJS:

```scala
libraryDependencies ++= Seq(
"com.beachape" %%% "enumeratum-anorm" % enumeratumAnormVersion
)
```

### Usage

#### Enum

If you need to store enum values in text column of following table

```sql
CREATE TABLE clothes (
shirt varchar(100)
)
```

you should use following code

```scala
import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with AnormEnum[ShirtSize] {
case object Small extends ShirtSize
case object Medium extends ShirtSize
case object Large extends ShirtSize

val values = findValues
}

case class Shirt(size: ShirtSize)

// ---

import java.sql.Connection
import anorm._

// Support Enum as readable Column
def findShirtSize(shirtId: String)(implicit con: Connection): ShirtSize =
SQL"SELECT size FROM shirt_tbl WHERE id = $shirtId".
as(SqlParser.scalar[ShirtSize].single)

// Support Enum as parameter
def updateShirtSize(shirtId: String, size: ShirtSize)(
implicit con: Connection) =
SQL"UPDATE shirt_tbl SET size = ${size} WHERE id = ${shirtId}".execute()
```

#### ValueEnum

```scala
import enumeratum.values.{ IntAnormValueEnum, IntEnum, IntEnumEntry }

sealed abstract class ShirtSize(val value: Int) extends IntEnumEntry

case object ShirtSize extends IntEnum[ShirtSize]
with IntAnormValueEnum[ShirtSize] {

case object Small extends ShirtSize(1)
case object Medium extends ShirtSize(2)
case object Large extends ShirtSize(3)

val values = findValues
}

case class Shirt(size: ShirtSize)

// ---

import java.sql.Connection
import anorm._

// Support ValueEnum as readable Column
def findShirtSize(shirtId: String)(implicit con: Connection): ShirtSize =
SQL"SELECT size FROM shirt_tbl WHERE id = $shirtId".
as(SqlParser.scalar[ShirtSize].single)

// Support ValueEnum as parameter
def updateShirtSize(shirtId: String, size: ShirtSize)(
implicit con: Connection) =
SQL"UPDATE shirt_tbl SET size = ${size} WHERE id = ${shirtId}".execute()
```

## Benchmarking

Benchmarking is in the unpublished `benchmarking` project. It uses JMH and you can run them in the sbt console by issuing the following command from your command line:
Expand Down
19 changes: 19 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ lazy val scala213ProjectRefs = Seq(
enumeratumScalacheckJs,
enumeratumPlayJsonJvm,
enumeratumPlayJsonJs,
enumeratumAnorm,
enumeratumArgonautJs,
enumeratumArgonautJvm,
enumeratumSlick,
Expand Down Expand Up @@ -125,6 +126,7 @@ lazy val scala211ProjectRefs = Seq(
enumeratumPlayJsonJvm,
// TODO drop 2.11 as play-json 2.7.x supporting Scala.js 1.x is unlikely?
// enumeratumPlayJsonJs, TODO re-enable once play-json supports Scala.js 1.0
enumeratumAnorm,
enumeratumArgonautJs,
enumeratumArgonautJvm,
enumeratumSlick,
Expand Down Expand Up @@ -159,6 +161,7 @@ lazy val integrationProjectRefs = Seq(
enumeratumCirceJs,
enumeratumCirceJvm,
enumeratumReactiveMongoBson,
enumeratumAnorm,
enumeratumArgonautJs,
enumeratumArgonautJvm,
enumeratumJson4s,
Expand Down Expand Up @@ -325,6 +328,22 @@ lazy val enumeratumPlay = Project(id = "enumeratum-play", base = file("enumeratu
.settings(withCompatUnmanagedSources(jsJvmCrossProject = false, includeTestSrcs = true): _*)
.dependsOn(enumeratumPlayJsonJvm % "compile->compile;test->test")

lazy val enumeratumAnorm = Project(id = "enumeratum-anorm", base = file("enumeratum-anorm"))
.settings(commonWithPublishSettings: _*)
.settings(testSettings: _*)
.settings(
version := "1.6.3-SNAPSHOT",
crossScalaVersions := Seq(
scala_2_11Version, scala_2_12Version, scala_2_13Version),
libraryDependencies ++=
Seq(
"org.playframework.anorm" %% "anorm" % "2.6.9",
"com.beachape" %% "enumeratum" % Versions.Core.stable,
//"com.beachape" %% "enumeratum-test" % Versions.Core.stable % Test
"org.eu.acolyte" %% "jdbc-scala" % "1.0.57" % Test
)
)

lazy val circeAggregate = aggregateProject("circe", enumeratumCirceJs, enumeratumCirceJvm)
lazy val enumeratumCirce = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
Expand Down
35 changes: 35 additions & 0 deletions enumeratum-anorm/src/main/scala/AnormColumn.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package enumeratum

import anorm.{Column, TypeDoesNotMatch}

private[enumeratum] object AnormColumn {
def column[A <: EnumEntry](enum: Enum[A], insensitive: Boolean): Column[A] =
if (insensitive) {
parse[A](enum)(enum.withNameInsensitiveOption)
} else {
parse[A](enum)(enum.withNameOption)
}

def lowercaseOnlyColumn[A <: EnumEntry](enum: Enum[A]): Column[A] =
parse[A](enum)(enum.withNameLowercaseOnlyOption)

def uppercaseOnlyColumn[A <: EnumEntry](enum: Enum[A]): Column[A] =
parse[A](enum)(enum.withNameUppercaseOnlyOption)

// ---

private def parse[A <: EnumEntry](enum: Enum[A])(extract: String => Option[A]): Column[A] =
Column.nonNull[A] {
case (s: String, _) =>
extract(s) match {
case Some(result) => Right(result)
case None => Left(TypeDoesNotMatch(s"Invalid value: $s"))
}

case (_, meta) =>
Left(
TypeDoesNotMatch(
s"Column '${meta.column.qualified}' expected to be String; Found ${meta.clazz}"))
}

}
20 changes: 20 additions & 0 deletions enumeratum-anorm/src/main/scala/AnormEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package enumeratum

import java.sql.PreparedStatement

import anorm.{Column, ToStatement}

/**
* Provides instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val column: Column[A] =
AnormColumn.column[A](self, insensitive = false)

implicit val toStatement: ToStatement[A] = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) = s.setString(i, v.entryName)
}
}
21 changes: 21 additions & 0 deletions enumeratum-anorm/src/main/scala/AnormInsensitiveEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package enumeratum

import java.sql.PreparedStatement

import anorm.{Column, ToStatement}

/**
* Provides insensitive instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormInsensitiveEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val column: Column[A] =
AnormColumn.column[A](self, insensitive = true)

implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName)
}
}
21 changes: 21 additions & 0 deletions enumeratum-anorm/src/main/scala/AnormLowercaseEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package enumeratum

import java.sql.PreparedStatement

import anorm.{Column, ToStatement}

/**
* Provides lowercase instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormLowercaseEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val column: Column[A] =
AnormColumn.lowercaseOnlyColumn[A](self)

implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName.toLowerCase)
}
}
21 changes: 21 additions & 0 deletions enumeratum-anorm/src/main/scala/AnormUppercaseEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package enumeratum

import java.sql.PreparedStatement

import anorm.{Column, ToStatement}

/**
* Provides uppercase instances for Anorm typeclasses:
*
* - [[anorm.Column]]
* - [[anorm.ToStatement]]
*/
trait AnormUppercaseEnum[A <: EnumEntry] { self: Enum[A] =>
implicit val column: Column[A] =
AnormColumn.uppercaseOnlyColumn[A](self)

implicit val toStatement = new ToStatement[A] {
def set(s: PreparedStatement, i: Int, v: A) =
s.setString(i, v.entryName.toUpperCase)
}
}
23 changes: 23 additions & 0 deletions enumeratum-anorm/src/main/scala/values/AnormColumn.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package enumeratum.values

import anorm.{Column, TypeDoesNotMatch}

private[values] object AnormColumn {
def apply[ValueType, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]
)(
implicit baseColumn: Column[ValueType]
): Column[EntryType] = Column.nonNull[EntryType] {
case (value, meta) =>
baseColumn(value, meta) match {
case Left(err) =>
Left(err)

case Right(s) =>
enum.withValueOpt(s) match {
case Some(obj) => Right(obj)
case None => Left(TypeDoesNotMatch(s"Invalid value: $s"))
}
}
}
}
16 changes: 16 additions & 0 deletions enumeratum-anorm/src/main/scala/values/AnormToStatement.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package enumeratum.values

import java.sql.PreparedStatement

import anorm.ToStatement

private[values] object AnormToStatement {
def apply[ValueType, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]
)(
implicit baseToStmt: ToStatement[ValueType]
): ToStatement[EntryType] = new ToStatement[EntryType] {
def set(s: PreparedStatement, i: Int, e: EntryType) =
baseToStmt.set(s, i, e.value)
}
}

0 comments on commit 415dcd6

Please sign in to comment.