У меня есть вариант использования, возможно, не специфичный для Slick, а для понимания. Шаблон высокого уровня похож на этот, но он приводит к ошибке компилятора в enum3:
(for {
enum1 <- // ...
enum2 <- // ...
enum3 <- optionArg.fold(empty result).map { ... }
} yield ())
Затем я делаю следующее, которое компилируется, но имеет дублирование кода, а именно enum1 и enum2:
optionArg match {
case (Some(arg)) => {
(for {
enum1 <- // ...
enum2 <- // ...
enum3 <- // do something with arg
} yield ())
}
case None => {
(for {
enum1 <- // ...
enum2 <- // ...
} yield ())
}
}
}
В конкретном случае у меня есть этот повторяющийся вариант:
/**
* Returns the inserted `oauth2Info` instance including the params. We first
* look up the `LoginInfo` by the relevant search criteria, fetching its `userId`
* which is then used to persist a `OAuth2Info` and multiple `OAuth2InfoParam`.
*
* @param extLoginInfo The login info for which the auth info should be added.
* @param extOAuth2Info The TOTP info to add containing the params.
* @return the inserted `oauth2Info` instance including the params.
*/
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val insertion = extOAuth2Info.params match {
case Some(params) => {
(for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
_ <- DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
} yield ())
}
case None => {
(for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
} yield ())
}
}
db.run(insertion.transactionally).map(_ => extOAuth2Info)
}
и этот краткий желаемый вариант не имеет:
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val insertion = (for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
_ <- extOAuth2Info.params.fold(DBIOAction.seq()) { params =>
DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
}
} yield ()).transactionally
db.run(insertion).map(_ => extOAuth2Info)
}
Выдает ошибку компиляции:
[play-silhouette-seed] $ compile
[info] Formatting 1 Scala source ProjectRef(uri("file:/home/skywalker/code/play-silhouette-seed/"), "root")(compile) ...
[info] Compiling 1 Scala source to /home/skywalker/code/play-silhouette-seed/target/scala-2.12/classes ...
[error] /home/skywalker/code/play-silhouette-seed/app/models/daos/OAuth2InfoDaoImpl.scala:58:28: type mismatch;
[error] found : slick.dbio.DBIOAction[scala.collection.immutable.Iterable[Int],slick.dbio.NoStream,slick.dbio.Effect.Write]
[error] required: slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect]
[error] DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed May 28, 2019 10:42:00 AM
Это проблема с fold
, когда компилятор не может вывести правильные общие типы для выражения. Он неправильно расширяет тип, возвращаемый первым выражением, до типа второго. Вам нужно немного помочь компилятору.
Бывает и в таких случаях
Some(123).fold(None)(_ => Some("123"))
// type mismatch; found: Some[String] required: None.type
Вы можете изменить fold
на map().getOrElse()
(тогда «хороший тип» будет первым и будет выведен).
Или вы можете где-нибудь добавить аннотации типа, например
private val noAction: slick.dbio.DBIOAction[Iterable[Int],NoStream,Effect.Write]
= DBIOAction.sequence()
theOption.fold(noAction)(params => .... )
Кроме того, если вы обнаружите, что дублируете код, это можно уменьшить, удалив общее выражение. С функциональным (без побочных эффектов) кодом, таким как Slick, такой рефакторинг вполне безопасен. Вы можете просто создать пару val
или def
с действиями Slick, которые вам нужно запустить, и составить их позже.
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val getUserId = LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
val insertOAuth = for {
userId <- getUserId
_ = (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
} yield userId
val insertion = extOAuth2Info.params match {
case Some(params) => {
(for {
userId <- insertOAuth
_ <- DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
} yield ())
}
case None =>
insertOAuth
}
db.run(insertion.transactionally).map(_ => extOAuth2Info)
}
Правильный! это было! Большое спасибо!