Yesod Advent Calendar 2016|18日目です。
Yesodは型安全を追求した実にHaskellらしいWebアプリケーションフレームワークですが、DBアクセスにも型安全が徹底されています。
DBに格納するモデルを定義するのが config/models
ファイルです。
stackのテンプレートを使って作った models ファイルは次のようになっています。
User
ident Text
password Text Maybe
UniqueUser ident
deriving Typeable
Email
email Text
userId UserId Maybe
verkey Text Maybe
UniqueEmail email
Comment json -- Adding "json" causes ToJSON and FromJSON instances to be derived.
message Text
userId UserId Maybe
deriving Eq
deriving Show
-- By default this file is used in Model.hs (which is imported by Foundation.hs)
nullになり得るフィールドには Maybe
と書かれて、実にHaskellらしい定義ファイルだと思います。
フィールドの型としては、上記で使われているText
の他にInt
やByteString
、Textarea
などが使えます。
さてフィールドの型には独自に定義した型が使えるので、方法を紹介します。
ここではサンプルとしてRDBのインスタンスを管理するモデルを作ってみます。
modelsファイルに以下のような定義を追加するとしましょう。
DbInstance
type DbType
DbType
というのが独自の型です。
型を定義する
定義する場所
独自の型を Model.hs
に書いてはいけません。TemplateHaskellの制限に引っかかり、コンパイルに失敗します。
ですので別ファイルを作らなければいけません。
ここでは、ModelDef.hs
というファイルを作ることにします。
ModelDef.hs
module ModelDef where
import GHC.Read
import GHC.Show
import Database.Persist.TH (derivePersistField)
data DbType = MySQL | PostgreSQL
deriving (Show, Read)
derivePersistField "DbType"
ポイントは derivePersistField
です。
modelsファイルで使う型は PersistField
という型クラスのインスタンスでなければいけません。しかしこれは面倒である程度定型的なプログラムになります。
そこでderivePersistField
関数を使うと、いい感じに処理してくれます。かなり助かります。
Model.hsに一工夫
ModelDef.hs
に定義した型を扱いやすくするために、Model.hs
に一工夫を加えましょう。
具体的には、ModelDef
モジュールを Model
モジュールとして公開します。
module Model where
という箇所を以下のように書き換えます。
module Model (
module Model
, module ModelDef
) where
import ModelDef
これで、ModelDefモジュールで定義された型をModelモジュールの型として扱えるようになります。
ここに紹介したサンプルは本当に基本的な物で、例えば独自に定義した型をJSONで扱えるようにする時は、もう少し記述が必要になります。
それはまたの機会にご紹介します。
cabalファイルにModelDefを追記
(2016.12.26追記)
これを忘れるととても怖いエラーになるのでした。
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.text+0xa02a): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType6_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.text+0xa035): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType1_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.text+0x1697f): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.text+0x15982): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbTypezuzdcfromPersistValue_info'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.data+0x3c78): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType1_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.data+0x3c80): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType6_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.data+0x3e58): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbTypezuzdcfromPersistValue_closure'
/vagrant/model-example/.stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/libHSmodel-example-0.0.0-IYcBfABB7oy20gCSpTfhLB.a(Model.o):(.data+0x3e98): undefined reference to `modelzuIYcBfABB7oy20gCSpTfhLB_ModelDef_zdfPersistFieldDbType_closure'
collect2: error: ld returned 1 exit status
Progress: 0/2
-- While building package model-example-0.0.0 using:
/home/vagrant/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.5.0-ghc-7.10.3 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.22.5.0 build lib:model-example exe:model-example test:test --ghc-options " -ddump-hi -ddump-to-file"
Process exited with code: ExitFailure 1
なんじゃこりゃ。
このエラーはcabalファイルに ModelDef
モジュールを追記し忘れていると起こります。
(修正前)
library
hs-source-dirs: ., app
exposed-modules: Application
Foundation
Import
Import.NoFoundation
Model
Settings
Settings.StaticFiles
Handler.Common
Handler.Home
Handler.Comment
Handler.Profile
(修正後)
library
hs-source-dirs: ., app
exposed-modules: Application
Foundation
Import
Import.NoFoundation
Model
ModelDef <<<< ここを追記
Settings
Settings.StaticFiles
Handler.Common
Handler.Home
Handler.Comment
Handler.Profile