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