No More SQL?Really?
・「SQL ならもう分かってるけど、このライブラリでどう
書けばいいか分からん... ドキュメントにないな... サンプル
探すか... ない... ソース読むか...」(時間の無駄)
・Anorm のコンセプト “You don’t need another DSL
to access relational databases.” 自体には共感する
・RDB 使ってて SQL と手を切れるなんて幻想ですよね
35
36.
ScalikeJDBC
・SQL 書きたいときは sql”selectname from
companies where id = ${id}” のように書く
・SQLInterpolation では必ずバインド変数になるので
SQL インジェクション脆弱性がないと断言できる
・1.6 から select.from(Company as c).where.eq(c.id,
123) のように SQL から乖離しない DSL をサポート
・QueryDSL は IDE での補完にも強い(Dynamic +
macros に時代が追いついてないけど)
36
37.
SQLInterpolation
val ids =Seq(1, 2, 3)
case class Member(id: Long, name: Option[String])
DB readOnly { implicit s =>
sql”select id, name from members where id in (${ids})”
.map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) }
.list.apply()
}
object Member extends SQLSyntaxSupport[Member]
val m = Member.syntax(“m”)
DB readOnly { implicit s =>
sql”select ${m.result.*} from ${Member as m} where ${m.id} in (${ids})”
.map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) }
.list.apply()
}
37
38.
QueryDSL
val ids =Seq(1, 2, 3)
case class Member(id: Long, name: Option[String])
object Member extends SQLSyntaxSupport[Member]
val m = Member.syntax(“m”)
DB readOnly { implicit s =>
withSQL { select.from(Members as m).where.in(m.id, ids) }
.map { rs => Member(
id = rs.long(m.resultName.id),
name = rs.stringOpt(m.resultName.name)) }
.list.apply()
}
38
いろんな SQL を...
//Squeryl
join(Student, Club.leftOuter)((s, c) =>
where(c.map(_.id) isNull)
select(s)
on(s.clubId === c.map(_.id)))
select * from students s
left join clubs c on s.club_id = s.id
where c.id is null
// Slick
for {
(s, c) <- Student leftJoin Club.map(_.??) on (_.clubId === _._1) if c._1.isNull
} yield (s, c)
// ScalikeJDBC
val (s, c) = (Student.syntax(“s”), Club.syntax(“c”))
select.from(Student as s).leftJoin(Club as c).on(s.clubId, c.id).where.isNull(c.id)
40
ScalaTest 例
・fixture パッケージのSpec であれば AutoRollback
trait を mixin して fixture と auto rollback が可能
class MemberSpec extends fixture.FlatSpec with AutoRollback {
// fixture loading before each test
override def fixture(implicit session: DBSession) {
sql”insert into members values (1, ‘Alice’)”.update.apply()
}
it should “work” in { implicit session =>
// do something and will be rolled back
}
}
45
46.
specs2 例
・unit は以下の通り、acceptanceスタイルもサポート
(ただし case class xxx() というちょっと変態的な...)
object MemberSpec extends Specification {
trait AutoRollbackWithFixture extends AutoRollback {
// fixture loading before each test
override def fixture(implicit session: DBSession) {
sql”insert into members values (1, ‘Alice’)”.update.apply()
}
}
“it should work” in new AutoRollbackWithFixture {
// do something and will be rolled back
}
46
SkinnyResource URIs
GET /members
GET/members/
GET /members.xml
GET /members.json
GET /members/{id}
GET /members/{id}.xml
GET /members/{id}.json
GET /members/new
POST /members
POST /members/
GET /members/{id}/edit
POST /members/{id}
PUT /members/{id}
PATCH /members/{id}
DELETE /members/{id}
62
Model
case class Member(id:Long, name: Option[String])
object Member extends SkinnyCRUDMapper[Member] {
def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member(
id = rs.long(m.id), name = rs.stringOpt(m.name)
)
}
Member.createWithAttribute(‘name -> “Alice”)
Member.findAllBy(sqls”name is not null”)
Member.where(‘name -> “Alice”).limit(10).offset(0)
val member = Member.findById(123)
member.map(_.copy(name = “Bob”).save())
Member.updateById(123).withAttributes(‘name -> “Bob”)
Member.deleteById(234)
67
68.
FactoryGirl
・factory_pal は instanceをつくるだけ、こっちは DB に
データ投入もしてくれる(本来の #create)
// src/test/resources/factories.conf
member {
name=”Anonymous”
}
// XXXSpec.scala
val anon: Member = FactoryGirl(Member).create()
val chris: Member = FactoryGirl(Member).create(‘name -> “Chris”)
val factory = FactoryGirl(Member).withValues(
‘createdAt -> new DateTime(2011, 6, 22, 13, 46))
val eric: Member = factory.create(‘name -> “Eric”)
68
69.
Associations
case class Member(id:Long, name: Option[String],
companyId: Option[Long], company: Option[Company] = None,
skills: Seq[Skill] = Nil)
object Member extends SkinnyCRUDMapper[Member] {
belongsTo[Company](Company, (m, c) => m.copy(company = c)).byDefault
val skills = hasManyThough[Skill](MemberSkill, Skill, (m, ss) => m.copy(skills ss))
def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member(
rs.long(m.id), rs.stringOpt(m.name), rs.longOpt(m.companyId)
)
}
Member.findById(123) // with company
Member.joins(Member.skills).findById(123) // with company, skills
69