Ошибка внедрения конструктора при сопоставлении Enumeration с использованием Slick 3.1 с play и scala

У меня есть следующие классы. обратите внимание на закомментированные строки ролей.

пользовательская модель:

case class User(
  uid: Option[Long] = None,
//  role: Option[Role.Role] = None,
  firstName: String,
  lastName: String,
  middleName: Option[String] = None,
  email: String,
  avatarUrl: Option[String],
  created: DateTime = DateTime.now,
  modified: DateTime = DateTime.now
)

Пользовательский компонент и UserDAO:

import javax.inject._
import scala.concurrent.Future
import org.krazykat.whatsupdoc.models.User
import play.api.db.slick.{HasDatabaseConfigProvider, DatabaseConfigProvider}
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import slick.driver.{JdbcProfile, JdbcDriver}
import org.joda.time.DateTime
import com.github.tototoshi.slick.GenericJodaSupport
import java.sql.Time

trait UsersComponent { self: HasDatabaseConfigProvider[JdbcProfile]  =>
  import driver.api._

  object PortableJodaSupport extends GenericJodaSupport(driver.asInstanceOf[JdbcDriver])
  import PortableJodaSupport._


//  implicit val roleMapper = MappedColumnType.base[Role.Role, String](
//    e => e.toString,
//    s => Role.withName(s)
//  )


  class Users(tag: Tag) extends Table[User](tag, "USERS") {

    def uid = column[Long]("USER_ID", O.PrimaryKey, O.AutoInc)
//    def role = column[Role.Role]("ROLE") 
    def firstName = column[String]("FIRST_NAME")
    def lastName = column[String]("LAST_NAME")
    def middleName = column[String]("MIDDLE_NAME")
    def email = column[String]("EMAIL")
    def avatarUrl = column[String]("AVATAR_URL")
    def created =  column[DateTime]("CREATED")
    def modified = column[DateTime]("MODIFIED")

    def * = (uid.?, firstName, lastName, middleName.?, email, avatarUrl.?, created, modified) <> (User.tupled, User.unapply _)
  }
}


/**
 * @author ehud
 */
@Singleton
class UsersDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends UsersComponent with HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  val users = TableQuery[Users]

  def count = db.run(users.length.result)

  def getById(uid: Long) = 
    db.run(users.filter(_.uid === uid).result.headOption)

  def insert(user: User) =
    db.run((users returning users.map(_.uid)) += user).map(id => id)

  def delete(user: User) =
    db.run(users.filter(_.uid === user.uid).delete)

  def update(uid: Long, user: User) = {
    val userToUpdate: User = user.copy(Some(uid))
    db.run(users.filter(_.uid === uid).update(userToUpdate))
  }
}

и тестовая спецификация:

class UsersSpec extends Specification {


  def dateIs(date: java.util.Date, str: String) = new java.text.SimpleDateFormat("yyyy-MM-dd").format(date) == str

  trait WithDatabaseConfig {
    lazy val (driver, db) = {
      val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
      (dbConfig.driver, dbConfig.db)
    }
  }

  "User model" should {   
    def usersDao(implicit app: Application) = {
      val app2UsersDAO = Application.instanceCache[UsersDAO]
      app2UsersDAO(app)
    }

    "be inserted to db correctly" in new WithApplication with WithDatabaseConfig {
      import driver.api._

      val userInsertresult = Await.result(usersDao.insert(
        User(None, "firstname", "lastname", Some("middlename"), "[email protected]", Some("avatar"), DateTime.now, DateTime.now)), 
        Duration.Inf
      )
      val count = Await.result(usersDao.count, Duration.Inf)
      count mustEqual 1
    }

когда я запускаю эту спецификацию, она преуспевает. здорово.

теперь я хочу добавить роль своему пользователю. поэтому я создал следующий класс ролей:

object Role extends Enumeration{

  type Role = Value

  val None = Value("NONE")
  val Admin = Value("ADMIN")
  val Root = Value("ROOT")
} 

и раскомментируйте соответствующие строки (и добавьте их к любому вызову экземпляра) и обновите файл эволюции (ролевая часть хранится как VARCHAR). теперь, когда спецификация запускается, я получаю следующее исключение:

[error]   ! be inserted to db correctly
[error]    Unable to provision, see the following errors:
[error]    
[error]    1) Error injecting constructor, java.lang.NullPointerException
[error]      at UsersDAO.<init>(UsersDAO.scala:59)
[error]      at UsersDAO.class(UsersDAO.scala:59)
[error]      while locating UsersDAO
[error]    
[error]    1 error (InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[error] com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[error] play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[error] play.utils.InlineCache.fresh(InlineCache.scala:69)
[error] play.utils.InlineCache.apply(InlineCache.scala:55)

Я пытался исследовать в Интернете, но все образцы Slick, которые я нашел о том, как делать Enumerations, кажутся моими, и ошибка инъекции не отображается в контексте Slick.

Есть идеи?


person Ehud Kaldor    schedule 03.11.2015    source источник


Ответы (2)


Проблема здесь в том, что вы пытаетесь использовать driver.api.MappedColumnType из черты HasDatabaseConfigProvider в черте UsersComponent. Но на момент инициализации UsersComponent драйвер еще не инжектирован, что вызывает NPE. Вы можете либо сделать свой roleMapper lazy, либо изменить его с val на def:

implicit lazy val roleMapper = ...

OR

implicit def roleMapper = ...
person Maksym Chernenko    schedule 19.01.2016

По какой-то особой причине вы выбираете Enumeration? Я не могу вам помочь, если это так, но я могу предложить использовать запечатанные черты + объекты case + sealerate.

Создайте компаньона для case class User и опишите Role

object User {
  sealed trait Role
  case object None extends Role
  case object Root extends Role
  case object Admin extends Role

  // A bit messy stuff to convert Role to String and back
  def s = sealerate.values[Role].foldLeft(Map[String, Role]()) { case (m, f) ⇒ m.updated(f.toString, f) }
  implicit val roleColumnType: JdbcType[Role] with BaseTypedType[Role] = MappedColumnType.base[Role, String](_.toString, s)
}

Добавить role в класс случаев User

case class User(
  uid: Option[Long] = None,
  role: Option[User.Role] = User.None,
  firstName: String,
  lastName: String,
  middleName: Option[String] = None,
  email: String,
  avatarUrl: Option[String],
  created: DateTime = DateTime.now,
  modified: DateTime = DateTime.now
)

Добавить role в таблицу Users

class Users(tag: Tag) extends Table[User](tag, "USERS") {
  ...
  def role = column[User.Role]("ROLE") 
  ...
}

Это все :)

Краткое объяснение перечислений Scala здесь.

person Anna Zubenko    schedule 04.11.2015
comment
это прекрасно работает, за исключением одного: UserComponent использует общий драйвер jdbc (trait UsersComponent { self: HasDatabaseConfigProvider[JdbcProfile] =›), а DAO внедряет dBConfigProvidor (см. выше). все это, чтобы я мог освободить зависимость от конкретного драйвера БД. проблема в том, что MappedColumnType.base[Role, String] является частью драйвера jdbc, поэтому мне также нужно сделать это общим. есть идеи? - person Ehud Kaldor; 05.11.2015
comment
Не уверен в этом, я использовал только определенный драйвер. Я вижу, что используются как HasDatabaseConfigProvider, так и MappedColumnType здесь. Это как-то помогает? - person Anna Zubenko; 05.11.2015