Scala ActiveRecord
The elegant ORM library for Scala
Author              github.com/y-yoshinoya

主にScalaとRuby on Railsの業務をやってます

Play Framework 2.0 を Beta バージョンから業務で
採用を試みる

 DBライブラリにはSqueryl, Anorm, ScalaQuery を
 採用

Scala ActiveRecord はより使えるDBライブラリを求
めた結果の産物
概要
Summary
Scala ActiveRecord
https://github.com/aselab/scala-activerecord


Latest version: 0.2.1
License: MIT
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”
Features               Version 0.2

Validations
Associations
Testing support
Improving query performance
Scala 2.10 support
背景
Background
Why created? (1)
Scalaの大半のDBライブラリはSQLをWrapしたもの

関数型言語としての方向性としては正しい、かつ合理
的な方法
   val selectCountries = SQL("Select * from Country")

   val countries = selectCountries().map(row =>
     row[String]("code") -> row[String]("name")
   ).toList


しかし、オブジェクト (Model) にマッピングするため
には全て自前で定義しないといけない
                     Not DRY!!
Why created? (2)
関連マッピングをまともに扱いたい・なるべく簡単に
使いたい

ClassごとにFinder methodを定義しなければならない
などDRYに書けない

本質的な処理についてだけ記述したいのにできない。
操作を書きたいのであってSQLを書きたいわけでは
ない

           Not DRY!!!
Other libraries
      Anorm
      Slick (ScalaQuery)
      Squeryl
(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...
(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.
(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.
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.
Improvements from Squeryl
Iterable#iterator にアクセスした時点で inTransaction す
るよう変更

save, delete 時にデフォルトで inTransaction するように

 もちろん明示的に transaction もできる

      // auto inTransaction
      query.toList
      model.save
      model.delete
Improvements from Squeryl
 関連設定ルールをCoCで結び付けられるように

 関連参照時のQueryがSubQueryにならないように

 Eager loadingを実装




     Simpler association definition rule.
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)
}
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]
 }
Minimal example
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]
 }
Create

 val person = Person("person1", 25)
 person.save
        true



val person = Person("person1", 25).create
       Person(“person1”, 25)
Read
Person.find(1)
       Some(Person(“person1”))

Person.toList
       List(Person(“person1”), ...)

Person.findBy(“name”, “john”)
      Some(Person(“john”))

* Type-safe approach
Person.where(_.name === “john”).headOption
      Some(Person(“john”))
Update
Person.find(1).foreach { p =>
  p.name = “aaa”
  p.age = 19
                     Callback hook
  p.save               Validations
}


Person.forceUpdate(_.id === 1)(
  _.name := “aa”, _.age := 19
)
Delete
Person.where(_.name === “john”)
      .foreach(_.delete)

Person.find(1) match {
  case Some(person) => person.delete
  case _ =>
}

Person.delete(1)
Query interface
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
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
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
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)
Limit and Offset
 Client.limit(10)

 Client.page(2, 5)


Existence of objects
Client.exists(_.name like "john%")
        true or false
Selecting specific fields
Client.select(_.name).toList
       List[String]



Client.select(c => (c.name, c.age)).toList
        List[(String, Int)]
Combining Queries
Clients.where(_.name like "john%”)
       .orderBy(_.age desc)
       .where(_.age.~ < 25)
       .page(2, 5)
       .toList
Select
  clients.name, clients.age, clients.id
From
  clients
Where
  ((clients.name like “john%”) and (clients.age < 25))
Order By
  clients.age Desc
limit 5 offset 2
Cache control
QueryからIterableに暗黙変換される際に取得したListを
キャッシュとして保持

  val orders = Order.where(_.age.~ > 20)

  // execute SQL query, and cached query
  orders.toList

  // non-execute SQL query.
  orders.toList
Validations
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]
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
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"
Callbacks
Available hooks
•beforeValidation()
•beforeCreate()
•afterCreate()
•beforeUpdate()
•afterUpdate()
•beforeSave()
•afterSave()
•beforeDelete()
•afterDelete()
Callback example
case class User(login: String) extends ActiveRecord {
  @Transient
  @Length(min=8, max=20)
  var password: String = _
  var hashedPassword: String = _

    override def beforeSave() {
      hashedPassword = SomeLibrary.encrypt(password)
    }
}

val user = User(“john”)
user.password = “raw_password”
user.save
        Storing encrypted password
Associations
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]
}
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")
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
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]
}
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”))
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]
  }
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)
}
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”)
}
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))
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))
Future
Future prospects

Compile time validation (using macro)
Serialization support
Web framework support
(Offers view helpers for Play 2.x and Scalatra)
STI, Polymorphic Association
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
Serialization support
  パーサを個別に定義することなく、モデルを
  定義するだけで済むように


                                 JSON
        Bind
Form               Model         XML
         View
        helper
                 Validation   MessagePack
View
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..
Thank you

Scala ActiveRecord

  • 1.
    Scala ActiveRecord The elegantORM library for Scala
  • 2.
    Author github.com/y-yoshinoya 主にScalaとRuby on Railsの業務をやってます Play Framework 2.0 を Beta バージョンから業務で 採用を試みる DBライブラリにはSqueryl, Anorm, ScalaQuery を 採用 Scala ActiveRecord はより使えるDBライブラリを求 めた結果の産物
  • 3.
  • 4.
  • 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
  • 7.
  • 8.
    Why created? (1) Scalaの大半のDBライブラリはSQLをWrapしたもの 関数型言語としての方向性としては正しい、かつ合理 的な方法 val selectCountries = SQL("Select * from Country") val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList しかし、オブジェクト (Model) にマッピングするため には全て自前で定義しないといけない Not DRY!!
  • 9.
    Why created? (2) 関連マッピングをまともに扱いたい・なるべく簡単に 使いたい ClassごとにFindermethodを定義しなければならない などDRYに書けない 本質的な処理についてだけ記述したいのにできない。 操作を書きたいのであってSQLを書きたいわけでは ない Not DRY!!!
  • 10.
    Other libraries Anorm Slick (ScalaQuery) Squeryl
  • 11.
    (1) Anorm ORMではなく、Model層を提供しない設計思想のため、 どうしてもClassごとに同じようなメソッドを定義せざ るを得なくなる caseclass 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の合成結果が単なるSubQueryにならないように    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.
  • 15.
    Improvements from Squeryl Iterable#iteratorにアクセスした時点で inTransaction す るよう変更 save, delete 時にデフォルトで inTransaction するように もちろん明示的に transaction もできる // auto inTransaction query.toList model.save model.delete
  • 16.
    Improvements from Squeryl 関連設定ルールをCoCで結び付けられるように 関連参照時のQueryがSubQueryにならないように Eager loadingを実装 Simpler association definition rule.
  • 17.
    Association definition (Squeryl) objectSchema 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] }
  • 19.
  • 20.
    Model implementation case classPerson(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)
  • 22.
    Read Person.find(1) Some(Person(“person1”)) Person.toList List(Person(“person1”), ...) Person.findBy(“name”, “john”) Some(Person(“john”)) * Type-safe approach Person.where(_.name === “john”).headOption Some(Person(“john”))
  • 23.
    Update Person.find(1).foreach { p=> p.name = “aaa” p.age = 19 Callback hook p.save Validations } Person.forceUpdate(_.id === 1)( _.name := “aa”, _.age := 19 )
  • 24.
    Delete Person.where(_.name === “john”) .foreach(_.delete) Person.find(1) match { case Some(person) => person.delete case _ => } Person.delete(1)
  • 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 valclient = 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
  • 31.
    Selecting specific fields Client.select(_.name).toList List[String] Client.select(c => (c.name, c.age)).toList List[(String, Int)]
  • 32.
    Combining Queries Clients.where(_.name like"john%”) .orderBy(_.age desc) .where(_.age.~ < 25) .page(2, 5) .toList Select clients.name, clients.age, clients.id From clients Where ((clients.name like “john%”) and (clients.age < 25)) Order By clients.age Desc limit 5 offset 2
  • 33.
    Cache control QueryからIterableに暗黙変換される際に取得したListを キャッシュとして保持 val orders = Order.where(_.age.~ > 20) // execute SQL query, and cached query orders.toList // non-execute SQL query. orders.toList
  • 34.
  • 35.
    Annotation-based Validation caseclass 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’snot 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 errorhandling... 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"
  • 38.
  • 39.
  • 40.
    Callback example case classUser(login: String) extends ActiveRecord { @Transient @Length(min=8, max=20) var password: String = _ var hashedPassword: String = _ override def beforeSave() { hashedPassword = SomeLibrary.encrypt(password) } } val user = User(“john”) user.password = “raw_password” user.save Storing encrypted password
  • 41.
  • 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 classUser(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) * Intermediatetable'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 classUser(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 classGroup(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))
  • 52.
  • 53.
    Future prospects Compile timevalidation (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..
  • 57.