Scala EnumerationとLift MappedEnum
■MappedEnum
LiftにはMappedEnumという型がある。型引数の1つでEnumerationの子クラスを取る。モデルクラスのフィールドにそれを指定すると、フォームではEnumerationの各要素がプルダウンで選択できるようになる。
MappedEnumのシグニチャは以下の通り。
abstract class MappedEnum[T<:Mapper[T], ENUM <: Enumeration] (val fieldOwner: T, val enum: ENUM) extends MappedField[ENUM#Value, T]
■Enumeration
ScalaのEnumerationはJavaのEnumと似たようなもの。イマイチ使いにくいという話を色々聞くものの、MappedEnumの引数にEnumerationを取るので仕方なく使う事に。
定義としては、通常は以下の感じで。Valueとかに関しては検索すると情報が色々出てくるはずなので説明は省略。
object Sex extends Enumeration { val Male, Female = Value }
今回は少し拡張して以下の通り(実際に使ってるコードより抜粋)。これやこれを参考にした。
object TemplateType extends Enumeration { val Basic = TemplateTypeVal("Basic") val Blank = TemplateTypeVal("Blank") val Mvc = TemplateTypeVal("MVC") val Xhtml = TemplateTypeVal("XHTML") // 独自のプロパティを持たせる case class TemplateTypeVal(name: String) extends Val(name) { val dirName = "lift_" + name.toLowerCase } implicit def valueToTemplateTypeValue(v: Value): TemplateTypeVal = v.asInstanceOf[TemplateTypeVal] }
■MappedEnumにEnumerationを渡す
モデルクラスの定義は以下のようにした(一部省略)。
class Foo extends LongKeyedMapper[Foo] with IdPK with CRUDify[Long, Foo] { object templateType extends MappedEnum(this, TemplateType) }
これでとりあえずは普通に動作するっぽい。
■問題
そのEnumerationの子クラス(先ほどの例で言うとTemplateType)を引数に取るメソッドがあって、そこにモデルクラスのフィールドの値を渡そうとするとコンパイルエラー。
こちらがコード
case class ClassA(v: TemplateType.Value) { // .... } object ClassA { // コンパニオンオブジェクト def apply(m: Foo) { this(m.templateType.is) } }
エラーメッセージは以下の通り。
[error] /project/src/main/scala/code/lib/ClassA.scala:nn: type mismatch; [error] found : somepackage.lib.TemplateType#Value [error] required: somepackage.lib.TemplateType.Value [error] this(m.templateType.is) [error] ^ [error] one error found
自分のScalaの師匠の1人によると、「A#Bは、object Aの内部クラスがBで、A.Bは、class Aの内部クラスがB」だそうだ。
■解決方法
以下の通りモデルクラスの定義を変更した。こちらを参考にした。
class Foo extends LongKeyedMapper[Foo] with IdPK with CRUDify[Long, Foo] { object templateType extends MappedEnum[Foo, TemplateType.type](this, TemplateType) }
ScalaはJavaに比べて型システムが柔軟で表現力が高いのはいいが、色々難しいな。