Skip to content

Instantly share code, notes, and snippets.

@ShahOdin
Last active June 16, 2019 18:02
Show Gist options
  • Save ShahOdin/bed148e9482fe6042042bc2c015c3a4b to your computer and use it in GitHub Desktop.
Save ShahOdin/bed148e9482fe6042042bc2c015c3a4b to your computer and use it in GitHub Desktop.
import org.scalatest.{FlatSpec, Matchers}
import cats.effect.{ContextShift, IO}
import com.dimafeng.testcontainers.{ForAllTestContainer, PostgreSQLContainer}
import doobie._
import implicits._
import com.itv.playground.datastore._
import JsonCRUDOps._
import io.circe.{Decoder, Encoder}
import scala.concurrent.ExecutionContext
object PGObjectUtils{
def jsonMetaCirce[T: Encoder: Decoder: TypeTag]: Meta[T] =
Meta.Advanced.other[PGobject]("json").timap[T](
pgO => decode[T](pgO.getValue).leftMap[T](e => throw e).merge)(
t => {
val o = new PGobject
o.setType("json")
o.setValue(t.asJson.noSpaces)
o
}
)
}
class JsonCRUDOps extends FlatSpec with Matchers with ForAllTestContainer {
override val container: PostgreSQLContainer = PostgreSQLContainer()
implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
private lazy val xa = container.transactor[IO]
"Doobie" should "allow inserting data as json, and querying it" in {
def insertCardSuitLegacy(cardSuit: CardSuit): ConnectionIO[CardSuit] = {
implicit val cardSuitMeta: Meta[CardSuit] = PGObjectUtils.jsonMetaCirce[CardSuit](
CardSuit.encoderById,
CardSuit.decoderById,
implicitly
)
sql"insert into suits (suit) values ($cardSuit)"
.update
.withUniqueGeneratedKeys[CardSuit]("id", "suit")
}
def insertCardSuit(cardSuit: CardSuit): ConnectionIO[CardSuit] = {
implicit val cardSuitMeta: Meta[CardSuit] = PGObjectUtils.jsonMetaCirce[CardSuit](
CardSuit.encoderByName,
CardSuit.decoderByName,
implicitly
)
sql"insert into person (suit) values ($cardSuit)"
.update
.withUniqueGeneratedKeys[CardSuit]("id", "suit")
}
val query = for {
_ <- dropSuit.transact(xa)
_ <- createSuit.transact(xa)
first <- insertCardSuitLegacy(Hearts).transact(xa)
second <- insertCardSuit(Spades).transact(xa)
} yield first == Hearts && second == Spades
query.attempt.unsafeRunSync() shouldBe Right(true)
}
}
object JsonCRUDOps {
import cats.syntax.either._
abstract class CardSuit(val name: String, val id: Short)
object CardSuit{
val decoderByName: Decoder[CardSuit] = Decoder[String].emap {
case Clubs.name => Clubs.asRight
case Diamonds.name => Diamonds.asRight
case Hearts.name => Hearts.asRight
case Spades.name => Spades.asRight
case _ => Left("Card suit stored in an invalid format.")
}
val encoderByName: Encoder[CardSuit] = Encoder[String].contramap(_.name)
val decoderById: Decoder[CardSuit] = Decoder[Short].emap {
case Clubs.id => Clubs.asRight
case Diamonds.id => Diamonds.asRight
case Hearts.id => Hearts.asRight
case Spades.id => Spades.asRight
case _ => Left("Card suit stored in an invalid format.")
}
val encoderById: Encoder[CardSuit] = Encoder[Short].contramap(_.id)
}
case object Clubs extends CardSuit("clubs", 1)
case object Diamonds extends CardSuit("diamonds", 2)
case object Hearts extends CardSuit("hearts", 3)
case object Spades extends CardSuit("spades", 4)
val dropSuit: ConnectionIO[Int] = sql"""
DROP TABLE IF EXISTS suits
""".update.run
val createSuit: ConnectionIO[Int] = sql"""
CREATE TABLE suits (
id SERIAL NOT NULL UNIQUE,
suit JSONB NOT NULL
)
""".update.run
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment