GitBucketで使っているSlick用のちょっとした便利機能

GitBucketではずっとSlickを使っているのですが、クエリを簡単に記述するためにちょっとした便利機能を追加しています。

1つめはこんな感じのエクストラクタ。以前吉田さんにTwitterかなにかで教えてもらったもの。

object ~ {
  def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t)
}

Slickだとjoinとかmapするときに以下のようにネストしたタプルを多用するのですが、

PullRequests
  .join(Issues).on { case (t1, t2) => 
    t1.issueId === t2.issueId)
  }
  .join(Accounts).on { case ((t1, t2), t3) => 
    t1.userName === t3.userName
  }
  .join(Accounts).on { case (((t1, t2), t3), t4) => 
    t2.userName === t4.userName
  }
  .joinLeft(Milestones).on { case ((((t1, t2), t3), t4), t5) =>
    t2.milestoneId === t5.milestoneId
  }

これを以下のようにすっきり記述することができます。テーブルを追加するときもカッコの数をあわせたりする必要がないので楽です。

PullRequests
  .join(Issues).on { case t1 ~ t2 => 
    t1.issueId === t2.issueId)
  }
  .join(Accounts).on { case t1 ~ t2 ~ t3 => 
    t1.userName === t3.userName
  }
  .join(Accounts).on { case t1 ~ t2 ~ t3 ~ t4 => 
    t2.userName === t4.userName
  }
  .joinLeft(Milestones).on { case t1 ~ t2 ~ t3 ~ t4 ~ t5 =>
    t2.milestoneId === t5.milestoneId
  }

2つめはRep[Boolean]に対するimplicitクラス。これは確か同僚が発明したものだったような気がします。

implicit class RichColumn(c1: Rep[Boolean]){
  def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] =
    if(guard) c1 && c2 else c1
}

これは条件によってfilterの内容を変更したい場合に使います。こんな感じ。

Accounts filter { t =>
  (1.bind         === 1.bind) &&
  (t.groupAccount === false.bind, !includeGroups) &&
  (t.removed      === false.bind, !includeRemoved)
} sortBy(_.userName)

第二引数(!includeGroupsとか!includeRemovedのところ)がtrueになる場合のみ条件が有効になります。検索フォームとか作るときに便利です。

ちょっとしたものですが、Slickでクエリ書くときに面倒に感じる部分なのでこれで少しは記述が楽になっているかなという感じです。