caprica
caprica

Reputation: 4146

How to trim an option string in a Slick column mapping when using Play Scala

What is the correct way, and where is the correct place, to trim a value from a fixed character database column when using the Play framework with Slick?

The reason I want to trim the string is that the database schema specifies a character(40) column type rather than a character varying type.

I have this case class:

case class Test(id: Long, trimMe: Option[String])

I have this Slick table relation:

class Tests(tag: Tag) extends Table[Test](tag, "test") {

  def id     = column[Long  ]("test_id", O.PrimaryKey, O.AutoInc)
  def trimMe = column[String]("trim_me"                         )

  def * = (id, trimMe) <> (Test.tupled, Test.unapply _)

}

I have this test class with a JSON mapping:

object TrimTest {

  implicit val testWrite = new Writes[Test] {
    def writes(test: Test) = Json.obj(
      "id"   -> test.id    ,
      "trim" -> test.trimMe
    )}
}

This all works but returns a space-padded string.

I tried a few variations of trimming in the JSON mapper:

object TrimTest {

  implicit val testWrite = new Writes[Test] {
    def writes(test: Test) = Json.obj(
      "id"    -> test.id                              ,
      "trim1" -> test.trimMe.map(_.trim)              ,
      "trim2" -> test.trimMe.fold("") {_.trim}        ,
      "trim3" -> test.trimMe.map(_.trim).getOrElse("")
    )}
}

trim1 above works, but returns null when the optional value is not present.

trim2 does work for all cases, but I have seen it stated in various places that map then getForElse is "more idiomatic Scala" than using fold.

trim3 is just past the limit of my current understanding of Scala and shows my intent but does not compile.

The compilation error in the case of trim3 is:

type mismatch; found : Object required: play.api.libs.json.Json.JsValueWrapper

I can certainly live with using fold, but what is the usual way to do this?

Upvotes: 1

Views: 1774

Answers (2)

Roman Kazanovskyi
Roman Kazanovskyi

Reputation: 3599

what about simple logic with pattern matching:

def trimOptionString(in: Option[String]): Option[String] = in.map(_.trim) match {
  case None => None
  case Some("") => None
  case Some(x) => Some(x)
}

Upvotes: 0

Ende Neu
Ende Neu

Reputation: 15783

What about getting the value and then trimming?

scala> val strOpt = Option("Some string not trimmed    ")
strOpt: Option[String] = Some(Some string not trimmed    )

scala> strOpt.getOrElse("").trim
res0: String = Some string not trimmed

scala> val strNone = Option(null)
strNone: Option[Null] = None

scala> strNone.getOrElse("").trim
res2: String = ""

Edit:

You have a Test row which is a case class with two fields, id and trimMe, you want to map this class to a JSON but the problem is you also want to trim it a the trimeMe field.

If you have a Column[String], from the Slick perspective it means a non optional field, but your case class has an optional field, either both (Test and Tests) have an optional trimMe field (that means a nullable column) or they don't (that means that the column is mandatory).

In the end the case class you have is the representation of a row you have in your database and that's what Slick queries return(*), it should reflect your schema declaration.

For the trim problem

  • if trimMe is a Column[Option[String]] your case class has trimMe: Option[String] so you can getOrElse and be sure to return a String and then trim,

    val someString = Option("some String  ")
    someString.getOrElse("").trim
    
  • if trimMe is a Column[String] you case class has trimMe: String, then just wrap it in an Option, then getOrElse and then trim:

    val str = "some other string  "
    Option(str).getOrElse("").trim
    

In this way wether it's a None or Some no exception is thrown.

(*) Not always, you can have queries which return columns instead of whole rows.

Upvotes: 1

Related Questions