Lift Mapperを拡張する

Lift Mapperの形が複雑に見えるので、Rails的タイムスタンプ付与を例題に、Mapper traitの拡張例を示しておきます。慣れれば簡単で、定型として覚えておくと便利なのではないでしょうか。

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 classMetaMapper 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の不思議機能を掘り下げられると思います。


コメント

コメントしてください
お名前:
入力しなければ「匿名さん」。20字以内。

メール:
入力しても表示しません

URL:
入力すればリンクが貼れます


コメント: