LoginSignup
5
0

More than 5 years have passed since last update.

Yesodのmodelsに独自定義した型を使いたいときには

Last updated at Posted at 2016-12-17

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の他にIntByteStringTextareaなどが使えます。

さてフィールドの型には独自に定義した型が使えるので、方法を紹介します。

ここではサンプルとしてRDBのインスタンスを管理するモデルを作ってみます。
modelsファイルに以下のような定義を追加するとしましょう。

DbInstance
    type DbType

DbTypeというのが独自の型です。

型を定義する

定義する場所

独自の型を Model.hs に書いてはいけません。TemplateHaskellの制限に引っかかり、コンパイルに失敗します。
ですので別ファイルを作らなければいけません。
ここでは、ModelDef.hsというファイルを作ることにします。

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モジュールとして公開します。

Model.hs
module Model where

という箇所を以下のように書き換えます。

Model.hs
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
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0