Search code examples
scalaplayframeworkslick

How to extract slick entities from play framework dao singleton


I've been creating a new project using Play framework, Slick and Postgresql and I don't understand how can I extract entities from DAO. Most tutorials show examples where entity (class extending table) is a class within dao singleton.

I've done actually the same:

import javax.inject.{Inject, Singleton}
import org.joda.time.DateTime
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile

import scala.concurrent.{ExecutionContext, Future}

@Singleton
class CardDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
                       (implicit executionContext: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {
  import dbConfig.profile.api._

  val Cards = TableQuery[CardsTable]

  private[dao] class CardsTable(tag: Tag) extends Table[Card](tag, "card") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def name = column[String]("name", O.Unique)
    def value = column[BigDecimal]("value")
    def currencyId = column[Int]("currency_id")
    def created = column[DateTime]("created")
    def active = column[Boolean]("active")

    override def * = (id, name, value, currencyId, created) <> (Card.tupled, Card.unapply)
  }

  def all(): Future[Seq[Card]] = db.run(Cards.result)
}

That's ok. I don't mind having an entity here but when I create another DAO I cannot access my TableQuery (joining tables or creating foreign keys) as it's just a field in singleton.

I was trying to extract CardsTable to separate class with companion object containing TableQuery but it turns out that column method, O, foreignKey come somewhere from HasDatabaseConfigProvider trait (importing dbConfig.profile.api._) so I'm not sure but I guess I have to pass dbConfig implicitly to the class.

How would you do that ? It's just the beginning of the project so really don't want to make some rookie mistakes at this point.


Solution

  • Thanks to Łukasz I've found a way how to do this:

    trait Tables {
      this: HasDatabaseConfigProvider[JdbcProfile] => {}
    
      import dbConfig.profile.api._
    
      val Cards = TableQuery[CardsTable]
      val FaceValues = TableQuery[FaceValuesTable]
      val Currencies = TableQuery[CurrenciesTable]
      val Cryptocurrencies = TableQuery[CryptocurrenciesTable]
      val Wallets = TableQuery[WalletsTable]
      val Transactions = TableQuery[TransactionsTable]
    
      class CryptocurrenciesTable(tag: Tag) extends Table[Cryptocurrency](tag, "cryptocurrency") with ActiveAndCreated[Cryptocurrency] {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def name = column[String]("name", O.Unique)
        def cryptocurrencyCode = column[String]("cryptocurrency_code", O.Unique)
        def blockchainExplorerUrl = column[String]("blockchain_explorer_url")
        def iconSvg = column[String]("icon_svg")
    
        override def * = (id, name, cryptocurrencyCode, blockchainExplorerUrl, iconSvg, created) <>
          (Cryptocurrency.tupled, Cryptocurrency.unapply)
      }
    
      class FaceValuesTable(tag: Tag) extends Table[FaceValue](tag, "face_value") with ActiveAndCreated[FaceValue] {
        def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
        def value = column[BigDecimal]("value")
        def currencyId = column[Int]("currency_id")
        def cardId = column[Int]("card_id")
    
        def card = foreignKey("card_fk", cardId, Cards)(_.id)
        def currency = foreignKey("currency_fk", currencyId, Currencies)(_.id)
    
        override def * = (id, value, currencyId, cardId) <> (FaceValue.tupled, FaceValue.unapply)
      }
    
    ...
    }
    

    Simple Dao trait:

    trait Dao extends HasDatabaseConfigProvider[JdbcProfile] with Tables
    

    And now all DAOs are very simple:

    @Singleton
    class WalletDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
                             (implicit executionContext: ExecutionContext) extends Dao {
      import dbConfig.profile.api._
    
      def find(walletAddress: WalletAddress): Future[Option[Wallet]] = {
        val query = for {
          wallet         <- Wallets
          cryptocurrency <- Cryptocurrencies if cryptocurrency.id === wallet.id &&
            cryptocurrency.cryptocurrencyCode === walletAddress.cryptocurrency
        } yield wallet
        db.run(query.result.headOption)
      }
    
      def find(walletId: Int): Future[Option[Wallet]] = db.run(Wallets.filter(_.id === walletId).result.headOption)
    
      def save(wallet: Wallet): Future[Int] = db.run(Wallets += wallet)
    }