Lift標準のORマッパーの例
package myproject.model
import _root_.net.liftweb.mapper._
import _root_.net.liftweb.util._
class Product extends LongKeyedMapper[Product] with IdPK {
def getSingleton = Product
object name extends MappedString(this, 255)
object price extends MappedInt(this)
def destroy = this.delete_!
}
object Product extends Product with LongKeyedMetaMapper[Product]{
override def dbTableName = "products"
def findByName(name:String) = findAll(By(Product.name,name))
}
LongKeyedMapper/LongKeyedMetaMapperは、Long型のIDを自動採番するマッパーです。
lift mapperは、Mapper classとMetaMapper objectのペアで表現します。一般的な言語のstaticメソッド/classメソッド(singleton)は、scalaではobjectで表現します。
find系のsingletonメソッドはMetaMapper objectに
エンティティに対するインスタンスメソッドはMapper class側に定義します。
使い方は以下のようなイメージになります。
Product.findAll.foreach( prod => prod.destroy )
Lift Mapperの拡張
ここで、LongKeyedMapper/LongKeyedMetaMapperをちょっと拡張してみます。Railsでよく知られている、エンティティ更新のタイムスタンプを自動付与するしくみを作ってみます。
package myproject.model
import _root_.java.util.Date
import _root_.net.liftweb.mapper._
trait MyCommonMapper[A <: MyCommonMapper[A]] extends LongKeyedMapper[A] {
self: A =>
object updated_at extends MappedDateTime(this)
object created_at extends MappedDateTime(this)
}
trait MyCommonMetaMapper[A <: MyCommonMapper[A]] extends LongKeyedMetaMapper[A] {
self: A =>
val fill_updated_at = (a:A) => { a.updated_at(new Date);() }
val fill_created_at = (a:A) => { a.created_at(new Date);() }
var before_create = List( fill_created_at )
var before_update = List( fill_updated_at )
override def beforeCreate = before_create
override def beforeSave = before_update
}
Mapper側でカラムを定義して、MetaMapper側でHook内に更新ロジックを定義します。
ちょっと型パラメータの関係が絡み合っていて困惑してしまうかもしれないですが、ここはめげずに観察しましょう。
これを継承したProductは、
class Product extends MyCommonMapper[Product] with IdPK {
: //同様
}
object Product extends Product with MyCommonMetaMapper[Product]{
// create hookの追加
before_create :::= List( ( self:Product )=> println(self) )
: //同様
}
これで、create/save時に、create_at/updated_atが自動で更新されます。
よりscalaらしく
継承ではなくMix-inで拡張するなら、LongKeyed はジャマになるので、もっと上位のMapper/MetaMapperを継承します。
trait TimestampMapper[A <: TimestampMapper[A]] extends Mapper[A] {
//MyCommonMapperと同様
}
trait TimestampMetaMapper[A <: TimestampMapper[A]] extends MetaMapper[A] {
//MyCommonMetaMapperと同様
}
class Product extends LongKeyedMapper[Product]
with TimestampMapper[Product] with IdPK {
//同様
}
object Product extends Product with LongKeyedMetaMapper[Product]
with TimestampMetaMapper[Product]{
//同様
}
型が複雑で拡張しずらく感じるかもしれませんが、定型だと思っておけば大丈夫。なんでこんな型になっているのかを考えてみると
view boundsやselftypeなど、scalaの不思議機能を掘り下げられると思います。