web-dev-qa-db-ja.com

データベースに依存しないPlayアプリケーションを作成して、初めてデータベースを初期化する方法は?

SlickPlay Framework 2.1を使用していますが、いくつか問題があります。

次のエンティティを考えると...

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 _)
}

...特定のデータベースドライバーのパッケージをインポートする必要がありますが、H2testingおよびPostgreSQLforproduction。どうすればいいですか?

私はユニットテストでドライバー設定をオーバーライドすることでこれを回避することができました:

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
      }
    }
  }
}

私はこのソリューションが気に入らず、DBに依存しないコードを書くための洗練された方法があるので、2つの異なるデータベースエンジンが使用されています。1つはテスト用で、もう1つは本番用です。

私も進化を使用したくないので、Slickにデータベーステーブルを作成させたいと思います。

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
    }
  }
}

最初にアプリケーションを起動すると、すべてが正常に動作します...もちろん、アプリケーションを2回目に起動すると、テーブルがすでにPostgreSQLデータベースに存在するため、アプリケーションがクラッシュします。

とはいえ、私の最後の2つの質問は次のとおりです。

  1. データベーステーブルが既に存在するかどうかを確認するにはどうすればよいですか?
  2. onStartメソッドをDB-agnosticの上に作成して、FakeApplicationでアプリケーションをテストできるようにするにはどうすればよいですか?
62
j3d

ケーキパターン/依存関係注入を使用して、データベースアクセスレイヤーからSlickドライバーを分離する方法の例は、ここ https://github.com/slick/slick-examples にあります。

FlickApplicationでSlickドライバーとテストアプリケーションを分離する方法

数日前、play用のSlick統合ライブラリを作成しました。これにより、ドライバーの依存関係がPlayプロジェクトのapplication.confに移動します: https://github.com/danieldietrich/slick-integration

このライブラリの助けを借りて、あなたの例は次のように実装されます:

1)project/Build.scalaに依存関係を追加します

"net.danieldietrich" %% "slick-integration" % "1.0-SNAPSHOT"

スナップショットリポジトリを追加

resolvers += "Daniel's Repository" at "http://danieldietrich.net/repository/snapshots"

または、slick-integrationがローカルで公開されている場合は、ローカルリポジトリ

resolvers += Resolver.mavenLocal

2)Slickドライバーをconf/application.confに追加します

slick.default.driver=scala.slick.driver.H2Driver

3)app/models/Account.scalaを実装します

スリック統合の場合、自動インクリメントされるLongタイプの主キーを使用することが想定されています。 pk名は 'id'です。 Table/Mapperの実装にはデフォルトのメソッド(delete、findAll、findById、insert、update)があります。エンティティは、 'insert'メソッドで必要な 'withId'を実装する必要があります。

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)app/models/DAL.scalaを実装します

これは、データベースにアクセスするためにコントローラーによって使用されるデータアクセス層(DAL)です。トランザクションは、対応するコンポーネント内のテーブル/マッパー実装によって処理されます。

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)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)
    }
  }

}

データベーステーブルが既に存在するかどうかを確認する方法

この質問に十分な答えを出すことはできません...

...しかし、おそらくこれは本当にあなたがやりたいことではありません。テーブルに属性を追加する場合はどうでしょう、たとえばAccount.active?現在テーブル内に格納されているデータを保護したい場合は、alterスクリプトが役立ちます。現在、そのような変更スクリプトは手動で作成する必要があります。 DAL.ddl.createStatementsを使用して、作成ステートメントを取得できます。以前のバージョンと比較できるように並べ替える必要があります。次に、差分(以前のバージョン)を使用して、変更スクリプトを手動で作成します。ここでは、dbスキーマを変更するために進化が使用されます。

(最初の)進化を生成する方法の例を次に示します。

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)

  }

}
39
Daniel Dietrich

私はこの問題にも対処しようとしていました。データベースをテストと本番の間で切り替える機能です。各テーブルオブジェクトを特性にラップするという考えは魅力的ではありませんでした。

ここでは、ケーキパターンの長所と短所については説明しませんが、興味のある方のための別の解決策を見つけました。

基本的には、次のようなオブジェクトを作成します。

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
    }
  }
}

もちろん、ここで好きな決定ロジックを実行できます。システムプロパティに基づく必要はありません。

今、代わりに:

import scala.slick.driver.H2Driver.simple._

あなたは言うことができます

import mypackage.MovableDriver.simple._

更新:Slick 3.0バージョン、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
    }
  }
}
28
triggerNZ

play-slick は、他の回答で提案されているものとまったく同じであり、Play/Typesafeの傘下にあるようです。

import play.api.db.slick.Config.driver.simple._をインポートするだけで、conf/application.confに従って適切なドライバが選択されます。

また、接続プーリング、DDL生成などの機能も提供します。

2