Where to put my database access methods when using

2019-08-07 10:32发布

问题:

(This question is based on a very similar previous request for help. With the introduction of a DAO and multiple database drivers, the same problem requires a different approach, and I hope warrants a new SO question.)

I have a class and Slick Table defined like this:

import play.api.db.slick.Profile

case class Foo(title: String, description: String, id: Int = 0)

trait FooComponent extends Profile { this: Profile =>
  import profile.simple._

  class FooTable(tag: Tag) extends Table[Foo](tag, "FOO") {

    def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
    def title = column[String]("TITLE", O.NotNull)
    def description = column[String]("DESCRIPTION")

    def * = (title, description, id) <> (Foo.tupled, Foo.unapply)
  }
}

And a data access object:

class DAO(override val profile: JdbcProfile) extends FooComponent with Profile {
  val foos = TableQuery[FooTable]
}

object current {
  val dao = new DAO(DB(play.api.Play.current).driver)
}

This is pretty awesome, because now I can add something like the following to my application.conf:

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

db.test.driver=org.postgresql.Driver
db.test.user="testuser"
db.test.password=""
db.test.url="jdbc:postgresql:testdb"

... and if I do the following in a Controller:

import models.current.dao._
import models.current.dao.profile.simple._

I have access to my foos TableQuery, and it automagically gets the driver and database url given for db.default in application.conf.

In a similar, but not-quite-as-nice way, I can do the following in my test Specification:

"test Foos" in new WithApplication() {
  val dao = new DAO(play.api.db.slick.DB("test").driver)
  import dao._ //import all our database Tables
  import dao.profile.simple._ //import specific database methods

  play.api.db.slick.DB("test").withSession { implicit s: Session =>
    println(s.conn.getMetaData.getURL)
    println(foos.list)
  }

However, what if I want to define a method which can act on a TableQuery[Foo]? Something like this:

def findByTitle(title: String) = foos.filter(_.id === id).list

Problem

What's the correct way of writing the findByTitle method, and where should I put it so that I can:

  • Call it in a way such that it won't collide with a method of the same name which acts on TableQuery[Bar]. Coming from OO, I feel like I want to do something like foos.findByTitle("someFoo"), but if there's a better way of doing this functional-style, I'm open to suggestions.
  • Call it from an application Controller such that the query will work with my db.default h2 driver, and from my test Specification so that it will work with my db.test postgres driver.

As an aside, if I can put this in my DAO:

object current {
  val dao = new DAO(DB(play.api.Play.current).driver)
}

and then import models.dao.current._ anywhere I want to use this DAO, how can I extend the same form to the following:

object test {
  val dao = new DAO(play.api.db.slick.DB("test").driver)
}

If I try to do this, the compiler complains about not having an implicit Application in scope.

回答1:

I think you need to read up in implicit conversion and implicit parameters in Scala. There are online Scala books available.

When you get an error message about a missing implicit it either means you ran into a failing type-check provided by a library preventing you from doing something wrong, but that's not the case here. Or you simply forgot to make the implicit available. There are two ways to make an implicit available. Either import it into the scope where you get the error message. Or basically defer the lookup to the callsite of your method. Not sure which one is the right one for play. You either need to import the implicit Application from play, or you need turn val dao into a method and request an implicit application in an implicit argument list def dao(implicit app: Application) = .... You can alternatively turn test into a class and request it there.



回答2:

If you use the play slick plugin it will need a started play application to be able to call code that uses the DB access from that plugin, you can make sure to start a play app in your tests using WithApplication as described in the docs: http://www.playframework.com/documentation/2.3.x/ScalaFunctionalTestingWithSpecs2