SlickでNullableを表現するには
ちょっと詰まったときがあったのを思い出して備忘録としてQiitaに綴ろうと思います。
下記が今回のサンプルのSlickで定義したスキーマです。
UsersRepo.scala
class UsersTable(tag: Tag) extends Table[User](tag,"USERS") {
def id = column[UUID]("ID", O.PrimaryKey)
def email = column[String]("EMAIL")
def hashedPassword = column[String]("HASHED_PASSWORD")
def totp = column[Option[String]]("TOTP")
def firstName = column[String]("FIRST_NAME")
def lastName = column[String]("LAST_NAME")
def birthday = column[java.sql.Date]("BIRTHDAY")
def major = column[String]("MAJOR")
def year = column[Int]("YEAR")
def profile = column[String]("PROFILE")
def adminFlag = column[Boolean]("ADMIN_FLAG")
def * = (
id,
email,
hashedPassword,
totp,
firstName,
lastName,
birthday,
major,
year,
profile,
adminFlag) <> (User.tupled, User.unapply)
def idxEmail = index("IDX_EMAIL", email.toUpperCase, unique = true)
}
またこのスキーマのドメインがこちら
User.scala
case class User(
id: java.util.UUID,
email: String,
hashedPassword: String,
totp: Option[String],
firstName: String,
lastName: String,
birthday: java.sql.Date,
major: String,
year: Int,
profile: Option[String],
adminFlag: Boolean)
object User {
val tupled = (apply _).tupled
def fromForm(
params: (String, String, String, java.sql.Date, String, Int),
email: String
) = apply(
UUID.randomUUID(),
email,
PasswordHasher.generate(params._1),
None,
params._2,
params._3,
params._4,
params._5,
params._6,
None,
false
)
}
今回、profileカラムをnullableにする場合の話です。UserコンパニオンオブジェクトでfromFormメソッドを定義しています。このメソッドは、DBにインサートする値を引数に渡した状態でapplyしてくれるメソッドです。
今回、この機能ではprofileカラムはNULLで保存したいと考え、Noneで渡しています。
NoneでインサートすることでSQLでは、NULLが発行されるようになっています。
1.つまり、NoneはOption型なので、あるカラムをNullableで設定したい場合は、そのカラムとなるドメインのフィールドの型をOption型にして上げる必要があります。
このままNoneにのみするとこのようなエラーが出ます。
[info] Compiling 1 Scala source to /Users/fujisawaryouhei/Development/Scala/msains/target/scala-2.12/classes ...
[error] /Users/fujisawaryouhei/Development/Scala/msains/app/models/repo/UsersRepo.scala:52:27: type mismatch;
[error] found : ((java.util.UUID, String, String, Option[String], String, String, java.sql.Date, String, Int, Option[String], Boolean)) => models.domain.User
[error] required: ((java.util.UUID, String, String, Option[String], String, String, java.sql.Date, String, Int, String, Boolean)) => ?
[error] adminFlag) <> (User.tupled, User.unapply)
[error] ^
[error] /Users/fujisawaryouhei/Development/Scala/msains/app/models/repo/UsersRepo.scala:52:40: type mismatch;
[error] found : Option[(java.util.UUID, String, String, Option[String], String, String, java.sql.Date, String, Int, Option[String], Boolean)]
[error] required: Option[(java.util.UUID, String, String, Option[String], String, String, java.sql.Date, String, Int, String, Boolean)]
[error] adminFlag) <> (User.tupled, User.unapply)
[error] ^
[error] two errors found
Optionとして認識してくれないようです。なぜでしょうか。。。
2.調べてみたところ、def * = 部分のprofile.?にしてあげると解決しました。
* ← この役割が未だに不明なので調べてみようと思います。
def * = (
id,
email,
hashedPassword,
totp,
firstName,
lastName,
birthday,
major,
year,
profile.?, //.?を追加すると解決する。
adminFlag) <> (User.tupled, User.unapply)