J'utilise Slick avec un Play Framework 2.1 et j'ai quelques problèmes.
Étant donné l'entité suivante ...
package models
import scala.slick.driver.PostgresDriver.simple._
case class Account(id: Option[Long], email: String, password: String)
object Accounts extends Table[Account]("account") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def email = column[String]("email")
def password = column[String]("password")
def * = id.? ~ email ~ password <> (Account, Account.unapply _)
}
... Je dois importer un package pour un pilote de base de données spécifique, mais je veux utiliser H2 pour test et PostgreSQL dans production . Comment dois-je procéder?
J'ai pu contourner cela en remplaçant les paramètres du pilote dans mon test unitaire:
package test
import org.specs2.mutable._
import play.api.test._
import play.api.test.Helpers._
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
import models.{Accounts, Account}
class AccountSpec extends Specification {
"An Account" should {
"be creatable" in {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
Accounts.ddl.create
Accounts.insert(Account(None, "[email protected]", "Password"))
val account = for (account <- Accounts) yield account
account.first.id.get mustEqual 1
}
}
}
}
Je n'aime pas cette solution et je me demande s'il existe une façon élégante d'écrire du code DB-agnostique, donc il y a deux moteurs de base de données différents utilisés - un en test et un autre en production?
Je ne veux pas non plus utiliser l'évolution, et je préfère laisser Slick créer les tables de base de données pour moi:
import play.api.Application
import play.api.GlobalSettings
import play.api.Play.current
import play.api.db.DB
import scala.slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
import models.Accounts
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
database withSession {
Accounts.ddl.create
}
}
}
La première fois que je démarre l'application, tout fonctionne bien ... puis, bien sûr, la deuxième fois que je démarre l'application, elle se bloque car les tables existent déjà dans la base de données PostgreSQL.
Cela dit, mes deux dernières questions sont les suivantes:
onStart
ci-dessus agnostique DB afin de pouvoir tester mon application avec FakeApplication
?Vous trouverez un exemple sur la façon d'utiliser l'injection de modèle/dépendance de gâteau pour découpler le pilote Slick de la couche d'accès à la base de données ici: https://github.com/slick/slick-examples .
Il y a quelques jours, j'ai écrit une bibliothèque d'intégration Slick pour play, qui déplace la dépendance du pilote vers le fichier application.conf du projet Play: https://github.com/danieldietrich/slick-integration .
Avec l'aide de cette bibliothèque, votre exemple serait implémenté comme suit:
1) Ajoutez la dépendance à project/Build.scala
"net.danieldietrich" %% "slick-integration" % "1.0-SNAPSHOT"
Ajouter un référentiel d'instantanés
resolvers += "Daniel's Repository" at "http://danieldietrich.net/repository/snapshots"
Ou référentiel local, si l'intégration simplifiée est publiée localement
resolvers += Resolver.mavenLocal
2) Ajoutez le pilote Slick à conf/application.conf
slick.default.driver=scala.slick.driver.H2Driver
3) Implémenter app/models/Account.scala
Dans le cas d'une intégration simplifiée, on suppose que vous utilisez des clés primaires de type Long qui sont incrémentées automatiquement. Le nom du pk est 'id'. L'implémentation de Table/Mapper a des méthodes par défaut (delete, findAll, findById, insert, update). Vos entités doivent implémenter 'withId' qui est nécessaire à la méthode 'insert'.
package models
import scala.slick.integration._
case class Account(id: Option[Long], email: String, password: String)
extends Entity[Account] {
// currently needed by Mapper.create to set the auto generated id
def withId(id: Long): Account = copy(id = Some(id))
}
// use cake pattern to 'inject' the Slick driver
trait AccountComponent extends _Component { self: Profile =>
import profile.simple._
object Accounts extends Mapper[Account]("account") {
// def id is defined in Mapper
def email = column[String]("email")
def password = column[String]("password")
def * = id.? ~ email ~ password <> (Account, Account.unapply _)
}
}
4) Implémenter app/models/DAL.scala
Il s'agit de la couche d'accès aux données (DAL) qui est utilisée par les contrôleurs pour accéder à la base de données. Les transactions sont gérées par l'implémentation Table/Mapper dans le composant correspondant.
package models
import scala.slick.integration.PlayProfile
import scala.slick.integration._DAL
import scala.slick.lifted.DDL
import play.api.Play.current
class DAL(dbName: String) extends _DAL with AccountComponent
/* with FooBarBazComponent */ with PlayProfile {
// trait Profile implementation
val profile = loadProfile(dbName)
def db = dbProvider(dbName)
// _DAL.ddl implementation
lazy val ddl: DDL = Accounts.ddl // ++ FooBarBazs.ddl
}
object DAL extends DAL("default")
5) Implémentez test/test/AccountSpec.scala
package test
import models._
import models.DAL._
import org.specs2.mutable.Specification
import play.api.test.FakeApplication
import play.api.test.Helpers._
import scala.slick.session.Session
class AccountSpec extends Specification {
def fakeApp[T](block: => T): T =
running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
Map("slick.default.driver" -> "scala.slick.driver.H2Driver",
"evolutionplugin" -> "disabled"))) {
try {
db.withSession { implicit s: Session =>
try {
create
block
} finally {
drop
}
}
}
}
"An Account" should {
"be creatable" in fakeApp {
val account = Accounts.insert(Account(None, "[email protected]", "Password"))
val id = account.id
id mustNotEqual None
Accounts.findById(id.get) mustEqual Some(account)
}
}
}
Je ne peux pas vous donner une réponse suffisante à cette question ...
... mais peut-être que ce n'est pas vraiment ce que vous voulez faire. Et si vous ajoutez un attribut à une table, dites Account.active
? Si vous souhaitez sécuriser les données actuellement stockées dans vos tables, un script alter fera le travail. Actuellement, un tel script de remplacement doit être écrit à la main. Le DAL.ddl.createStatements
pourrait être utilisé pour récupérer les instructions create. Ils doivent être triés pour être mieux comparables aux versions précédentes. Ensuite, un diff (avec la version précédente) est utilisé pour créer manuellement le script alter. Ici, les évolutions sont utilisées pour modifier le schéma db.
Voici un exemple sur la façon de générer (la première) évolution:
object EvolutionGenerator extends App {
import models.DAL
import play.api.test._
import play.api.test.Helpers._
running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
Map("slick.default.driver" -> "scala.slick.driver.PostgresDriver",
"evolutionplugin" -> "disabled"))) {
val evolution = (
"""|# --- !Ups
|""" + DAL.ddl.createStatements.mkString("\n", ";\n\n", ";\n") +
"""|
|# --- !Downs
|""" + DAL.ddl.dropStatements.mkString("\n", ";\n\n", ";\n")).stripMargin
println(evolution)
}
}
J'essayais également de résoudre ce problème: la possibilité de basculer les bases de données entre le test et la production. L'idée d'envelopper chaque objet de table dans un trait n'était pas attrayante.
Je n'essaie pas de discuter des avantages et des inconvénients du modèle de gâteau ici, mais j'ai trouvé une autre solution, pour ceux qui sont intéressés.
Fondamentalement, créez un objet comme celui-ci:
package mypackage
import scala.slick.driver.H2Driver
import scala.slick.driver.ExtendedProfile
import scala.slick.driver.PostgresDriver
object MovableDriver {
val simple = profile.simple
lazy val profile: ExtendedProfile = {
sys.env.get("database") match {
case Some("postgres") => PostgresDriver
case _ => H2Driver
}
}
}
De toute évidence, vous pouvez faire la logique de décision que vous aimez ici. Il ne doit pas être basé sur les propriétés du système.
Maintenant, au lieu de:
import scala.slick.driver.H2Driver.simple._
Tu peux dire
import mypackage.MovableDriver.simple._
MISE À JOUR: Une version Slick 3.0, gracieuseté de trent-ahrens:
package mypackage
import com.typesafe.config.ConfigFactory
import scala.slick.driver.{H2Driver, JdbcDriver, MySQLDriver}
object AgnosticDriver {
val simple = profile.simple
lazy val profile: JdbcDriver = {
sys.env.get("DB_ENVIRONMENT") match {
case Some(e) => ConfigFactory.load().getString(s"$e.slickDriver") match {
case "scala.slick.driver.H2Driver" => H2Driver
case "scala.slick.driver.MySQLDriver" => MySQLDriver
}
case _ => H2Driver
}
}
}
Le play-slick fait exactement la même chose que ce qui est proposé dans les autres réponses, et il semble être sous l'égide de Play/Typesafe.
Vous pouvez simplement importer import play.api.db.slick.Config.driver.simple._
et il choisira le pilote approprié en fonction de conf/application.conf
.
Il offre également plus de choses comme le pool de connexions, la génération DDL ...