Невозможно создать экземпляр Read для типа User. Типа недоразумение с Дубом ie в Scala - PullRequest
1 голос
/ 07 мая 2020

Я пытаюсь вернуть запись пользователя из базы данных, используя doob ie, http4s и cats. Я оказался в тупике из-за системы типов, которая выдает следующую ошибку на основе приведенного ниже кода:

маршрутизатор:

val httpRoutes = HttpRoutes.of[IO] {
    case GET -> Root / "second" / id =>
      val intId : Integer = Integer.parseInt(id)
      //if i make thie ConnectionIO[Option[Unit]] it compiles, but returns a cats Free object
      val userOption: ConnectionIO[Option[User]] = UserModel.findById(intId, transactor.transactor)
      Ok(s"userOption is instance of: ${userOption.getClass} object: ${userOption.toString}")
  }.orNotFound

модель:

case class User(
                 id: Read[Integer],
                 username: Read[String],
                 email: Read[String],
                 passwordHash: Read[String], //PasswordHash[SCrypt],
                 isActive: Read[Boolean],
                 dob: Read[Date]
               ) {
//  def verifyPassword(password: String) : VerificationStatus = SCrypt.checkpw[cats.Id](password, passwordHash)
}

object UserModel {
  def findById[User: Read](id: Integer, transactor: Transactor[ConnectionIO]): ConnectionIO[Option[User]] = findBy(fr"id = ${id.toString}", transactor)

  private def findBy[User: Read](by: Fragment, transactor: Transactor[ConnectionIO]): ConnectionIO[Option[User]] = {
    (sql"SELECT id, username, email, password_hash, is_active, dob FROM public.user WHERE " ++ by)
      .query[User]
      .option
      .transact(transactor)
  }
}

Ошибка :

Error:(35, 70) Cannot find or construct a Read instance for type:
  core.model.User
This can happen for a few reasons, but the most common case is that a data
member somewhere within this type doesn't have a Get instance in scope. Here are
some debugging hints:
- For Option types, ensure that a Read instance is in scope for the non-Option
  version.
- For types you expect to map to a single column ensure that a Get instance is
  in scope.
- For case classes, HLists, and shapeless records ensure that each element
  has a Read instance in scope.
- Lather, rinse, repeat, recursively until you find the problematic bit.
You can check that an instance exists for Read in the REPL or in your code:
  scala> Read[Foo]
and similarly with Get:
  scala> Get[Foo]
And find the missing instance and construct it as needed. Refer to Chapter 12
of the book of doobie for more information.
      val userOption: ConnectionIO[Option[User]] = UserModel.findById(intId, transactor.transactor)

Если я изменю строку на ConnectionIO [Option [User] на ConnectionIO [Option [Unit]], она компилируется и запускается, но возвращает объект Free (...) из библиотеки cats, который Я не смог понять, как разбирать, и не понимаю, почему я не могу вернуть свой класс дела!

См. Также объявления типов в методах findBy и findById. Перед тем, как я добавил их, была ошибка компиляции, в которой говорилось, что обнаружен пользователь, но требуется Read [User]. Я попытался применить объявление того же типа к вызову findById в маршрутизаторе, но он дал ту же ошибку, указанную выше.

Заранее благодарим вас за вашу помощь и, пожалуйста, проявите терпение, учитывая мое невежество. Я никогда не встречал более умной системы типов, чем я!

1 Ответ

1 голос
/ 07 мая 2020

Здесь есть что распаковать ...

  1. Вам не нужно заключать поля в User в Read.
  2. Параметризация функций с помощью User не является обязательным, поскольку вы знаете, какой тип возвращаете.
  3. В большинстве случаев, если вы вручную обрабатываете экземпляры Read, вы делаете что-то неправильно. Создание экземпляра Read полезно только в том случае, если данные, которые вы читаете, напрямую не соответствуют вашему типу.
  4. Transactor предназначен для преобразования из ConnectionIO (некоторые действия над JDB C connection) с какой-либо другой монадой (например, IO), вызывая соединение, выполняя действие в транзакции и удаляя указанное действие. Transactor[ConnectionIO] на самом деле не имеет большого смысла в этом и, вероятно, может привести к тупикам (поскольку вы в конечном итоге попытаетесь вызвать соединение, пока удерживаете его). Просто напишите свою базу данных logi c в ConnectionIO и transact все это потом.
  5. Integer не используется в коде Scala, кроме взаимодействия с Java и Doob ie не имеет для него экземпляров Get / Put.
  6. В ваших маршрутах вы берете ConnectionIO[Option[User]] и делаете .toString. Это не делает того, что вы хотите, - просто превращает созданное вами действие в бесполезную строку, фактически не оценивая его. Чтобы на самом деле получить Option[User], вам необходимо оценить свое действие.

Собирая все это вместе, мы получаем такой фрагмент кода:

import java.util.Date
import cats.effect.IO
import doobie.{ConnectionIO, Fragment, Transactor}
import doobie.implicits._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.syntax.kleisli._

def httpRoutes(transactor: Transactor[IO]) = HttpRoutes.of[IO] {
  case GET -> Root / "second" / IntVar(intId) =>
    UserModel.findById(intId)
      .transact(transactor)
      .flatMap { userOption =>
        Ok(s"userOption is instance of: ${userOption.getClass} object: ${userOption.toString}")
      }
}.orNotFound

final case class User(
  id: Int,
  username: String,
  email: String,
  passwordHash: String,
  isActive: Boolean,
  dob: Date
)

object UserModel {
  def findById(id: Int): ConnectionIO[Option[User]] = findBy(fr"id = ${id.toString}")

  private def findBy(by: Fragment): ConnectionIO[Option[User]] =
    (sql"SELECT id, username, email, password_hash, is_active, dob FROM public.user WHERE " ++ by)
      .query[User]
      .option
}

userOption вот Option[User].

...