5. Features Version 0.1
Squeryl wrapper
Type-safe (most part)
Rails ActiveRecord-like operability
CoC (Convention over Configuration)
DRY (Don't Repeat Yourself) principles.
Auto transaction control
“Type-safed ActiveRecord model for Scala”
6. Features Version 0.2
Validations
Associations
Testing support
Improving query performance
Scala 2.10 support
11. (1) Anorm
ORMではなく、Model層を提供しない設計思想のため、
どうしてもClassごとに同じようなメソッドを定義せざ
るを得なくなる
case class Person(id: Pk[Long], name: String)
object Person {
def create(person: Person): Unit = {
DB.withConnection { implicit connection =>
SQL("insert into person(name) values ({name})").on(
'name -> person.name).executeUpdate()
}
}
...
}
Not DRY...
12. (2) Slick (ScalaQuery)
Queryの使用感は良いが、テーブル定義がやや冗長。
Modelとマッピングする場合、その対応をテーブルごと
に明示的に記述する必要がある
case class Member(id: Int, name: String, email: Option[String])
object Members extends Table[Member]("MEMBERS") {
def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("NAME")
def email = column[Option[String]]("EMAIL")
def * = id.? ~ name ~ email <> (Member, Member.unapply _)
}
Query interface is Good.
But, not DRY defining tables.
13. (3) Squeryl
ScalaのORMとしては最も良い出来
Queryに対してさらに条件を指定したQueryを作成すると
Sub-QueryなSQLが呼び出される
val query = from(table)(t => where(t.id.~ > 20) select(t))
from(query)(t => where(t.name like “%test%”) select(t))
Select * From
(Select * From table Where table.id > 20) q1
Where q1.name like “test”
Very nice ORM library.
Need to be aware of the SQL performance.
14. Improvements from Squeryl
Queryの合成結果が単なるSub Queryにならないように
Queryの条件をパフォーマンス劣化せず流用可能
val query = Table.where(_.id.~ > 20)
query.where(_.name like “%test%”).toList
Select * From table
Where
table.id > 20 and table.name like “test”
Generates more simple SQL statement.
16. Improvements from Squeryl
関連設定ルールをCoCで結び付けられるように
関連参照時のQueryがSubQueryにならないように
Eager loadingを実装
Simpler association definition rule.
17. Association definition (Squeryl)
object Schema extends Schema {
val foo = table[Foo]
val bar = table[Bar]
val fooToBar = oneToManyRelation(Foo, Bar).via(
(f, b) => f.barId === b.id
)
}
class Foo(var barId: Long) extends SomeEntity {
lazy val bar: ManyToOne[Bar] =
schema.fooToBar.right(this)
}
class Bar(var bar: String) extends SomeEntity {
lazy val foos: OneToMany[Foo] =
schema.fooToBar.left(this)
}
18. Association definition
(Scala ActiveRecord)
object Tables extends ActiveRecordTables {
val foo = table[Foo]
val bar = table[Bar]
}
class Foo(var barId: Long) extends ActiveRecord {
lazy val bar = belongsTo[Bar]
}
class Bar(var bar: String) extends ActiveRecord {
lazy val foos = hasMany[Foo]
}
20. Model implementation
case class Person(var name: String, var age: Int)
extends ActiveRecord
object Person
extends ActiveRecordCompanion[Person]
Schema definition
object Tables extends ActiveRecordTables {
val people = table[Person]
}
21. Create
val person = Person("person1", 25)
person.save
true
val person = Person("person1", 25).create
Person(“person1”, 25)
26. Single object finder
val client = Client.find(10)
Some(Client) or None
val john = Client.findBy("name", "john")
Some(Client("john")) or None
val john25 = Client.findBy(("name", "john"), ("age", 25))
Some(Client("john", 25)) or None
27. Multiple object finder
Clients.where(c =>
c.name === "john" and c.age.~ > 25
).toList
Clients.where(_.name === "john")
.where(_.age.~ > 25)
.toList
Select
clients.name, clients.age, clients.id
From
clients
Where
clients.name = “john” and clients.age > 25
28. Using `Iterable` methods
val client = Client.head
First Client or RecordNotFoundException
val client = Client.lastOption
Some(Last Client) or None
val (adults, children) = Client.partition(_.age >= 20)
Parts of clients
29. Ordering
* Simple order (ORDER BY client.name)
Client.orderBy(_.name)
* Set order (use for 'asc' or 'desc')
Client.orderBy(_.name asc)
* Ordering by multiple fields
Client.orderBy(_.name asc, _.age desc)
30. Limit and Offset
Client.limit(10)
Client.page(2, 5)
Existence of objects
Client.exists(_.name like "john%")
true or false
35. Annotation-based Validation
case class User(
@Required name: String,
@Length(max=20) profile: String,
@Range(min=0, max=150) age: Int
) extends ActiveRecord
object User extends
ActiveRecordCompanion[User]
36. Validation Sample
// it’s not save in the database
// because the object is not valid
val user = User("", “Profile”, 25).create
user.isValid
false
user.hasErrors
true
user.errors.messges
Seq("Name is required")
user.hasError("name")
true
37. More functional error handling...
User("John", “profile”, 20).saveEither match {
case Right(user) => println(user.name)
case Left(errors) => println(errors.messages)
}
"John"
User("", “profile”, 15).saveEither match {
case Right(user) => println(user.name)
case Left(errors) => println(errors.messages)
}
"Name is required"
42. One-to-Many
case class User(name: String) extends ActiveRecord {
val groupId: Option[Long] = None
lazy val group = belongsTo[Group]
}
case class Group(name: String) extends ActiveRecord {
lazy val users = hasMany[User]
}
43. One-to-Many
val user1 = User("user1").create
val user2 = User("user2").create
val group1 = Group("group1").create
group1.users << user1
group1.users.toList
List(User("user1"))
user1.group.getOrElse(Group(“group2”))
Group("group1")
44. Association is Queryable
group1.users.where(_.name like “user%”)
.orderBy(_.id desc)
.limit(5)
.toList
Select
users.name, users.id
From
users
Where
((users.group_id = 1) AND (users.name like “user%”))
Order By
users.id Desc
limit 5 offset 0
45. Many-to-Many (HABTM)
case class User(name: String) extends ActiveRecord {
lazy val groups = hasAndBelongsToMany[Group]
}
case class Group(name: String) extends ActiveRecord {
lazy val users = hasAndBelongsToMany[User]
}
46. Many-to-Many (HABTM)
val user1 = User("user1").create
val user2 = User("user2").create
val group1 = Group("group1").create
val group2 = Group("group2").create
user1.groups := List(group1, group2)
user1.groups.toList
List(Group(“group1”), Group(“group2”))
group1.users.toList
List(User(“user1”))
47. Many-to-Many (hasManyThrough)
* Intermediate table's model
case class Membership(
userId: Long,
projectId: Long,
isAdmin: Boolean = false
) extends ActiveRecord
{
lazy val user = belongsTo[User]
lazy val group = belongsTo[Group]
}
48. Many-to-Many (hasManyThrough)
case class User(name: String) extends ActiveRecord {
lazy val memberships = hasMany[Membership]
lazy val groups =
hasManyThrough[Group, Membership](memberships)
}
case class Group(name: String) extends ActiveRecord {
lazy val memberships = hasMany[Membership]
lazy val users =
hasManyThrough[User, Membership](memberships)
}
49. Conditions option
case class Group(name: String) extends ActiveRecord {
lazy val adminUsers =
hasMany[User](conditions = Map("isAdmin" -> true))
}
group.adminUsers << user
user.isAdmin == true
ForeignKey option
case class Comment(name: String) extends ActiveRecord {
val authorId: Long
lazy val author =
belongsTo[User](foreignKey = “authorId”)
}
50. Joining tables
Client.joins[Order](
(client, order) => client.id === order.clientId
).where(
(client, order) => client.age.~ < 20 and order.price.~ > 1000
).select(
(client, order) => (client.name, client.age, order.price)
).toList
Select
clients.name, clients.age, orders.price
From
clients inner join orders on (clients.id = orders.client_id)
Where
((clients.age < 20) and (groups.price > 1000))
51. Eager loading associations
Solution to N + 1 queries problem
Order.includes(_.client).limit(10).map {
order => order.client.name
}.mkString(“n”)
Select orders.price, orders.id From orders limit 10 offset 0;
Select
clients.name, clients.age, clients.id
From
clients inner join orders on (clients.id = orders.client_id)
Where
(orders.id in (1,2,3,4,5,6,7,8,9,10))
53. Future prospects
Compile time validation (using macro)
Serialization support
Web framework support
(Offers view helpers for Play 2.x and Scalatra)
STI, Polymorphic Association
54. Compile time validation
(using macro)
型安全性が確保できていない部分について
Scala macro を利用した型安全化
ActiveRecord#findBy(key: String, value: Any)
Association( fe
conditions: Map[String, Any], e- sa
foreignKey: String ttyp
) No
Type-safe binding configuration
55. Serialization support
パーサを個別に定義することなく、モデルを
定義するだけで済むように
JSON
Bind
Form Model XML
View
helper
Validation MessagePack
View
56. Web framework support
CRUD controller
Form helper
scala-activerecord-play2
scala-activerecord-scalatra
Code generator Controller, Model, View
sbt generate scaffold Person name:string:required age:int
scala-activerecord-play2-sbt-plugin
scala-activerecord-scalatra-sbt-plugin etc..