Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enumeratum-quill #170

Merged
merged 8 commits into from
Feb 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 89 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Integrations are available for:
- [Argonaut](http://argonaut.io): JVM and ScalaJS
- [Json4s](http://json4s.org): JVM only
- [ScalaCheck](https://www.scalacheck.org): JVM and ScalaJS
- [Quill](http://getquill.io): JVM and ScalaJS

### Table of Contents

Expand All @@ -54,8 +55,9 @@ Integrations are available for:
10. [Json4s integration](#json4s)
11. [Slick integration](#slick-integration)
12. [ScalaCheck](#scalacheck)
13. [Benchmarking](#benchmarking)
14. [Publishing](#publishing)
13. [Quill integration](#quill)
14. [Benchmarking](#benchmarking)
15. [Publishing](#publishing)


## Quick start
Expand Down Expand Up @@ -848,6 +850,91 @@ Similarly, you can get `Arbitrary` and `Cogen` instances for every `ValueEnum` s
import enumeratum.values.scalacheck._
```

## Quill
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-quill_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.beachape/enumeratum-quill_2.11)

### SBT

To use enumeratum with [Quill](http://getquill.io):

```scala
libraryDependencies ++= Seq(
"com.beachape" %% "enumeratum-quill" % enumeratumQuillVersion
)
```

To use with ScalaJS:

```scala
libraryDependencies ++= Seq(
"com.beachape" %%% "enumeratum-quill" % enumeratumQuillVersion
)
```

### Usage

#### Enum

```scala
import enumeratum._

sealed trait ShirtSize extends EnumEntry

case object ShirtSize extends Enum[ShirtSize] with QuillEnum[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 io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
```
- Note that an explicit cast to the `EnumEntry` trait (eg. `ShirtSize.Small: ShirtSize`) is required when binding hardcoded `EnumEntry`s

#### ValueEnum

```scala
import enumeratum._

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

case object ShirtSize extends IntEnum[ShirtSize] with IntQuillEnum[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 io.getquill._

lazy val ctx = new PostgresJdbcContext(SnakeCase, "ctx")
import ctx._

ctx.run(query[Shirt].insert(_.size -> lift(ShirtSize.Small: ShirtSize)))

ctx.run(query[Shirt]).foreach(println)
```
- Note that an explicit cast to the `ValueEnumEntry` abstract class (eg. `ShirtSize.Small: ShirtSize`) is required when binding hardcoded `ValueEnumEntry`s
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No big deal; but this isn't an explicit cast; just an explicit type annotation :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I knew that it wasn't an explicit cast (asInstanceOf), but I forgot what it was called, thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, if we were to be pedantic, I guess this should be called a Type Ascription instead of a Type Annotation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you missed out on one of the "explicit cast"s. I've opened a PR (#172) to fix both.

- `quill-cassandra` currently does not support `ShortEnum` and `ByteEnum` (see [getquill/quill#1009](https://github.com/getquill/quill/issues/1009))
- `quill-orientdb` currently does not support `ByteEnum` (see [getquill/quill#1029](https://github.com/getquill/quill/issues/1029))

## Slick integration

[Slick](http://slick.lightbend.com) doesn't have a separate integration at the moment. You just have to provide a `MappedColumnType` for each database column that should be represented as an enum on the Scala side.
Expand Down
28 changes: 28 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ lazy val circeVersion = "0.9.0"
lazy val uPickleVersion = "0.4.4"
lazy val argonautVersion = "6.2"
lazy val json4sVersion = "3.5.1"
lazy val quillVersion = "2.3.2"

def thePlayVersion(scalaVersion: String) =
CrossVersion.partialVersion(scalaVersion) match {
Expand Down Expand Up @@ -320,6 +321,33 @@ lazy val enumeratumScalacheck = crossProject
lazy val enumeratumScalacheckJs = enumeratumScalacheck.js
lazy val enumeratumScalacheckJvm = enumeratumScalacheck.jvm

lazy val quillAggregate = aggregateProject("quill", enumeratumQuillJs, enumeratumQuillJvm)
lazy val enumeratumQuill = crossProject
.crossType(CrossType.Pure)
.in(file("enumeratum-quill"))
.settings(commonWithPublishSettings: _*)
.settings(testSettings: _*)
.settings(
name := "enumeratum-quill",
version := "0.1.0-SNAPSHOT",
libraryDependencies ++= {
import org.scalajs.sbtplugin._
val cross = {
if (ScalaJSPlugin.autoImport.jsDependencies.?.value.isDefined)
ScalaJSCrossVersion.binary
else
CrossVersion.binary
}
Seq(
impl.ScalaJSGroupID.withCross("io.getquill", "quill-core", cross) % quillVersion,
impl.ScalaJSGroupID.withCross("io.getquill", "quill-sql", cross) % quillVersion % Test,
impl.ScalaJSGroupID.withCross("com.beachape", "enumeratum", cross) % Versions.Core.stable
)
}
)
lazy val enumeratumQuillJs = enumeratumQuill.js
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL: Quill works with ScalaJS O_o

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha I was shocked as well

lazy val enumeratumQuillJvm = enumeratumQuill.jvm

lazy val commonSettings = Seq(
organization := "com.beachape",
scalafmtOnCompile := true,
Expand Down
15 changes: 15 additions & 0 deletions enumeratum-quill/src/main/scala/enumeratum/Quill.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package enumeratum

import io.getquill.MappedEncoding

object Quill {
/**
* Returns an Encoder for the given enum
*/
def encoder[A <: EnumEntry](enum: Enum[A]): MappedEncoding[A, String] = MappedEncoding(_.entryName)

/**
* Returns a Decoder for the given enum
*/
def decoder[A <: EnumEntry](enum: Enum[A]): MappedEncoding[String, A] = MappedEncoding(enum.withName)
}
45 changes: 45 additions & 0 deletions enumeratum-quill/src/main/scala/enumeratum/QuillEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package enumeratum

import io.getquill.MappedEncoding

/**
* Helper trait that adds implicit Quill encoders and decoders for an [[Enum]]'s members
*
* Example:
*
* {{{
* scala> import enumeratum._
* scala> import io.getquill._
*
* scala> sealed trait ShirtSize extends EnumEntry
* scala> case object ShirtSize extends Enum[ShirtSize] with QuillEnum[ShirtSize] {
* | case object Small extends ShirtSize
* | case object Medium extends ShirtSize
* | case object Large extends ShirtSize
* | val values = findValues
* | }
*
* scala> case class Shirt(size: ShirtSize)
*
* scala> val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
* scala> import ctx._
*
* scala> val size: ShirtSize = ShirtSize.Small
*
* scala> ctx.run(query[Shirt].insert(_.size -> lift(size))).string
* res0: String = INSERT INTO Shirt (size) VALUES (?)
* }}}
*/
trait QuillEnum[A <: EnumEntry] { this: Enum[A] =>

/**
* Implicit Encoder for this enum
*/
implicit lazy val enumEncoder: MappedEncoding[A, String] = Quill.encoder(this)

/**
* Implicit Decoder for this enum
*/
implicit lazy val enumDecoder: MappedEncoding[String, A] = Quill.decoder(this)

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

import io.getquill.MappedEncoding

object Quill {

/**
* Returns an Encoder for the provided ValueEnum
*/
def encoder[ValueType, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]
): MappedEncoding[EntryType, ValueType] = MappedEncoding(_.value)

/**
* Returns a Decoder for the provided ValueEnum
*/
def decoder[ValueType, EntryType <: ValueEnumEntry[ValueType]](
enum: ValueEnum[ValueType, EntryType]
): MappedEncoding[ValueType, EntryType] = MappedEncoding(enum.withValue)
}
102 changes: 102 additions & 0 deletions enumeratum-quill/src/main/scala/enumeratum/values/QuillValueEnum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package enumeratum.values

import io.getquill.MappedEncoding

sealed trait QuillValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType], QuillType] {
this: ValueEnum[ValueType, EntryType] =>

/**
* Implicit Encoder for this enum
*/
implicit val quillEncoder: MappedEncoding[EntryType, QuillType]

/**
* Implicit Decoder for this enum
*/
implicit val quillDecoder: MappedEncoding[QuillType, EntryType]
}

/**
* QuillEnum for IntEnumEntry
*
* {{{
* scala> import enumeratum.values._
* scala> import io.getquill._
*
* scala> sealed abstract class ShirtSize(val value:Int) extends IntEnumEntry
* scala> case object ShirtSize extends IntEnum[ShirtSize] with IntQuillEnum[ShirtSize] {
* | case object Small extends ShirtSize(1)
* | case object Medium extends ShirtSize(2)
* | case object Large extends ShirtSize(3)
* | val values = findValues
* | }
*
* scala> case class Shirt(size: ShirtSize)
*
* scala> val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
* scala> import ctx._
*
* scala> val size: ShirtSize = ShirtSize.Small
*
* scala> ctx.run(query[Shirt].insert(_.size -> lift(size))).string
* res0: String = INSERT INTO Shirt (size) VALUES (?)
* }}}
*/
trait IntQuillEnum[EntryType <: IntEnumEntry] extends QuillValueEnum[Int, EntryType, Int] {
this: ValueEnum[Int, EntryType] =>
implicit val quillEncoder: MappedEncoding[EntryType, Int] = Quill.encoder(this)
implicit val quillDecoder: MappedEncoding[Int, EntryType] = Quill.decoder(this)
}

/**
* QuillEnum for LongEnumEntry
*/
trait LongQuillEnum[EntryType <: LongEnumEntry] extends QuillValueEnum[Long, EntryType, Long] {
this: ValueEnum[Long, EntryType] =>
implicit val quillEncoder: MappedEncoding[EntryType, Long] = Quill.encoder(this)
implicit val quillDecoder: MappedEncoding[Long, EntryType] = Quill.decoder(this)
}

/**
* QuillEnum for ShortEnumEntry
*/
trait ShortQuillEnum[EntryType <: ShortEnumEntry] extends QuillValueEnum[Short, EntryType, Short] {
this: ValueEnum[Short, EntryType] =>
implicit val quillEncoder: MappedEncoding[EntryType, Short] = Quill.encoder(this)
implicit val quillDecoder: MappedEncoding[Short, EntryType] = Quill.decoder(this)
}

/**
* QuillEnum for StringEnumEntry
*/
trait StringQuillEnum[EntryType <: StringEnumEntry] extends QuillValueEnum[String, EntryType, String] {
this: ValueEnum[String, EntryType] =>
implicit val quillEncoder: MappedEncoding[EntryType, String] = Quill.encoder(this)
implicit val quillDecoder: MappedEncoding[String, EntryType] = Quill.decoder(this)
}

/**
* QuillEnum for CharEnumEntry
*/
trait CharQuillEnum[EntryType <: CharEnumEntry] extends QuillValueEnum[Char, EntryType, String] {
this: ValueEnum[Char, EntryType] =>

/**
* Because all existing Quill contexts do not have built-in Encoders for Char, convert it to a String instead.
*/
implicit val quillEncoder: MappedEncoding[EntryType, String] = MappedEncoding(enum => String.valueOf(enum.value))

/**
* Because all existing Quill contexts do not have built-in Decoders for Char, convert it from a String instead.
*/
implicit val quillDecoder: MappedEncoding[String, EntryType] = MappedEncoding(str => withValue(str.charAt(0)))
}

/**
* QuillEnum for ByteEnumEntry
*/
trait ByteQuillEnum[EntryType <: ByteEnumEntry] extends QuillValueEnum[Byte, EntryType, Byte] {
this: ValueEnum[Byte, EntryType] =>
implicit val quillEncoder: MappedEncoding[EntryType, Byte] = Quill.encoder(this)
implicit val quillDecoder: MappedEncoding[Byte, EntryType] = Quill.decoder(this)
}
47 changes: 47 additions & 0 deletions enumeratum-quill/src/test/scala/enumeratum/QuillEnumSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package enumeratum

import org.scalatest.{FunSpec, Matchers}

import scala.collection.immutable

class QuillEnumSpec extends FunSpec with Matchers {

describe("A QuillEnum") {

// we only need to test whether it can compile because Quill will fail compilation if an Encoder is not found
it("should encode to String") {
"""
| import io.getquill._
| val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
| import ctx._
| ctx.run(query[QuillShirt].insert(_.size -> lift(QuillShirtSize.Small: QuillShirtSize)))
""".stripMargin should compile
}

// we only need to test whether it can compile because Quill will fail compilation if a Decoder is not found
it("should decode from String") {
"""
| import io.getquill._
| val ctx = new SqlMirrorContext(MirrorSqlDialect, Literal)
| import ctx._
| ctx.run(query[QuillShirt])
""".stripMargin should compile
}

}

}

sealed trait QuillShirtSize extends EnumEntry

case object QuillShirtSize extends Enum[QuillShirtSize] with QuillEnum[QuillShirtSize] {

case object Small extends QuillShirtSize
case object Medium extends QuillShirtSize
case object Large extends QuillShirtSize

override val values: immutable.IndexedSeq[QuillShirtSize] = findValues

}

case class QuillShirt(size: QuillShirtSize)
Loading