Slick3野郎の備忘録１

# Slick3野郎の備忘録１

**ドキュメント類**

  * [公式ドキュメント(翻訳版)](http://krrrr38.github.io/slick-doc-ja/v3.0.out/slick-doc-ja+3.0.html) 
  * [slick3.1.1 document](http://slick.lightbend.com/doc/3.1.1/queries.html)
  * [slick-multidb](https://www.lightbend.com/activator/template/slick-multidb-3.1)
  * [slick-akka-http](https://www.lightbend.com/activator/template/slick-akka-http)
  * [hello slick](https://www.lightbend.com/activator/template/hello-slick-3.1)
  * [plain sql](https://www.lightbend.com/activator/template/slick-plainsql-3.1)

**SQLの実行方法あれやこれや**

基本は

```main.scala
do.run(<action>)
```

action部分

```action.scala
//insert系
Users += User(3, "charles", "charles@example.com")
//update系
Users.filter { _.name.startsWith("alice") }.map(_.name).update("alice-updated")       
//delete系
Users.filter{ _.name === "alice" }.delete
```

group byとかこんな感じ　[参考にしたサイト](http://kxbmap.hatenablog.com/entry/2013/05/25/183014)

```groupby.sql
SELECT s.club_id, s.classroom, count(*)
  FROM Sutudents s
  GROUP BY s.club_id, u.classroom
```

↓slickで書くと

```slick.scala
val q3 = for {
  ((ci, cr), ss) <- Students.groupBy(s => (s.clubId, s.classroom))
} yield (ci, cr, ss.length)

val r3: List[(Option[Long], String, Int)] = q3.list()
```

[stackoverflow リレーション状態の結果を取得する方法](http://stackoverflow.com/questions/35258379/slick-3-return-custom-case-class-from-query)


**joinしてGroupByするケース　ドキュメント少なッ！**

```main.scala

// 入れ物の型を用意します
// parents, childs は別途modelが存在する想定です
case class ChildSummaryEntity(childsCount: Int)
case class ParentEntity(id: Option[Long], parentname: String, summary: Option[ChildSummaryEntity])

def getNestedEntityByGroup(parentId: Long): Future[Seq[ParentEntity]] = {

    val sampleId: Long = 159
    val a = 
      // where句
      parents.filter( _.id === parentId ).flatMap(p =>
      childs.filter( _.parentId === p.id ).map((p, _ )))
      // gourp by節 ここで2つ指定するとselect句のPはTuple型になるので注意
      .groupBy (u => (p._1.id, p._1.parentname))
      
      // select句
      .map { case (p, c) => (
        p._1,
        p._2,
        c.map(_._1).length)} // select column
      .result

    db.run(a.transactionally).flatMap { r =>
      Future.successful {
        r.map {
          // select句の項目を元にネストオブジェクトを作成する
          case (i, n, s) => ParentEntity(i, n, Some(ChildSummaryEntity(s)) )
        }
      }
    }
  }
```

#### 注意点
joinLeftなどで外部結合する場合、Left側はOption扱いになるので注意が必要。
LeftサイドのColumnを扱う場合はmap(_.hoge)ではなくflatMap(_.hoge)とする必要がある。
それはなぜか？
mapを使った場合、出力される生SQLを観察すると一目瞭然だが、射影に不要なcase文が混入する場合がある。


**slick3におけるflatMapについての理解**

[判りやすいblog](http://mashi.exciton.jp/archives/189)


**生SQL**

[公式: slick-plainsql](https://www.lightbend.com/activator/template/slick-plainsql-3.1#code/src/main/scala/Transfer.scala)

サービストレイトに実装する場合を前提に進めます

1. 利用部分のサンプルはこちら

```route.scala
trait FuckServiceRoute  extends FuckService  {
// (_.toJson)でJSON変換する時のフォーマット用implict
implicit val fuckFormat = jsonFormat3(FuckEntity)
trait FuckRoute  extends FuckService {
  val fuckRoute = pathPrefix("fuck") {
    get {
      complete(getFuckByRawSQL.map(_.toJson))
    }
  }
}
```

2. 実行部分のTraitはこちら

```transfer.scala
import models.FuckTransfer
trait FuckService extends FuckTable with FuckTransfer {
  // マッピングはFuckTransfer.FuckEntity側でimplictで定義するのでここでは何も気にする必要なし
  def getFuckByRawSQL() : Future[Seq[FuckEntity]]={
    // 文字列補完をしてみるslick側でSQLインジェクション対応までやってくれます
    val s = "fuck"   
    val a = sql"SELECT id, $s, created_at FROM fuck".as[FuckEntity]
    db.run(a)
  }
}
```

3. トランスファーのTraitはこちら

```model.scala
package models
import slick.jdbc.GetResult
import org.joda.time.DateTime
// 素のslickには、jodatimeのimplictがないので追加、これがないとDateTimeは利用できないぜ
import com.github.tototoshi.slick.MySQLJodaSupport._
trait FuckTransfer {
  case class FuckEntity(userId: Long, fuck: String, createAt: DateTime)
  //これだけで勝手にマッピングしてくれますが、順番大事 名前大事
  implicit val getFuckEntityResult = GetResult(r => FuckEntity(r.<<, r.<<, r.<<))
}
```


**トランザクション**

[公式ドキュメント](http://slick.typesafe.com/doc/3.0.0/dbio.html#transactions-and-pinned-sessions)

[ロールバック stackoverflow 動作未確認](http://stackoverflow.com/questions/34905455/how-can-i-roll-back-an-integration-test-with-slick-3-specs2)

```transactionnally.scala
  //トランザクション中で複数の処理を事項する場合
  def getAcidTest(): Future[Unit]  ={
    db.run(
      DBIO.seq(
        // みかんを蜜柑に変えて値段を変更（Item丸ごと更新）
        items.filter(_.id === 2).update(Item(2, "蜜柑", 250)),
        // 蜜柑をミカンに変更（nameだけを更新）
        items.filter(_.id === 2).map(_.name).update("ミカン"),
        // idが2より大きい物のpriceを800に（複数レコードをまとめて更新）
        items.filter(_.id > 2).map(_.price).update(800)
      ).transactionally
    )
  }
  ```
  
  db.runで実行するactionは一つなので複数の処理が必要な場合は**アクション合成**する
  ※○!=trueなら処理するなど
1. [qiita](http://qiita.com/torao@github/items/bee252b979ddb631885f)
2. [stackoverflow](http://stackoverflow.com/questions/34905455/how-can-i-roll-back-an-integration-test-with-slick-3-specs2)

**つまり複数の処理を行う場合は全てアクション合成する必要がある**
検索結果をもとに計算処理をし、生SQLを発行して結果を合成する時も同じ（逆に面倒くさいわっ！）
[stack overflow](http://stackoverflow.com/questions/31103813/executing-non-database-actions-in-a-transaction-in-slick-3)


**アクション合成**

[公式ドキュメント](http://krrrr38.github.io/slick-doc-ja/v3.0.out/Plain+SQL%E3%82%AF%E3%82%A8%E3%83%AA.html)


**注意点**

1. ジェネリック系の雛型に出来るのは「case class」だけ
2. slickのmodelはトレイト


[Slick3野郎の備忘録2へつづく](http://qiita.com/uedashuhei/items/70cae110681294c09295)