wvogel日記

自分用の技術備忘録が多めです.

Yesodで作る,業務外社内ツール

この記事はHaskell(その3) Advent Calendar 2017の22日目の記事になります.

「ああ,Haskellで仕事できたらどれだけ幸せか!」

おそらくHaskell好きな方にはこういった方が多いと思います.
ですが,実情としてC++C#, Pythonといった主力勢力の多い企業や団体の中で,一人Haskellを推していったところで焼け石に水
なぜでしょうか?それは,Haskellが共通言語ではないからです.
これはみなさんご承知でしょう.
なので,社内・社外の勉強会を利用するわけですが,如何せん,すでに他言語で書かれているものを置き換えたり機能追加するのはやはりハードルが高いわけです.
なぜでしょうか?
彼らはきっとこういうでしょう.
「だって,Pythonの方が慣れてるし」

慣れ.
そう,つまり慣れなのです!
私たちだって,英語は読み書きできるけれど,日常の多くのやりとりは日本語ですよね.なぜ?だって.慣れてるから,意思疎通も速いし円滑だからです.

Haskellを知ることではなく,慣れてもらうこと.
英語の単語帳をめくるのではなく,留学すること.
これはほとんど同じ行為です.
でも,業務で使う社内ツールはほとんどが他言語製.
では,業務外の社内ツールをHaskellで固めていきましょう!!

前置きが長くなりましたが,今回のお題は,「お弁当注文管理ツール」です.

【背景(私の職場)】
・職員のうち注文したい人の分をとりまとめ,毎朝,弁当屋さんに電話発注
・注文希望は紙に記入
・弁当大臣職員が紙の情報を整理し,電話
・支払いは各人,弁当大臣に月末に払う

ええ,ええ,プログラマな皆さんが思うことはわかります!
そうですよね,ださいですよね!!私が思うだけでも問題は多々あります.

・別の人の欄に記入できる
・紙が小さいので,記入修正を入れた場合,正しい値がわからなくなる
・そのため,金額集計が誤る可能性がある
・弁当大臣はフレックス勤務できない
・午前だけ出張の場合注文できない

というわけで,今回はYesodで作っていきます.

stack new lunch yesod-postgres
stack build yesod-bin cabal-install --instal-ghc
stack build

これでPostgreSQLを使ったYesodアプリケーション開発の準備完了.

まずはメンバ登録.

こんな感じに,PostgreSQLで仮のユーザー登録をしておきます.
パスワードはとりあえず弁当大臣にのみ付与しました.
では,ログインを実装しましょう.
とはいえ,このサンプルプログラムではすでに実装されていますので,少し書き変えるだけで動きます.
本当であればlogin formも自作したいところですが,まだ仕様を固めていないので,仮置きです.
まず,DBを読むための設定値をconfig/settingsに設定します.

database:
  user:     "_env:PGUSER:pyrite"
  password: "_env:PGPASS:pyrite"
  host:     "_env:PGHOST:localhost"
  port:     "_env:PGPORT:5432"
  # See config/test-settings.yml for an override during tests
  database: "_env:PGDATABASE:phosphophyllite"
  poolsize: "_env:PGPOOLSIZE:30"
copyright: Fine Lunch Make You Fun

データベース名はphosphophyllite,管理者をpyriteとしました.

DB形式はcondfig/modelsに,以下のように記入します.

User
    ident Text
    password Text Maybe
    UniqueUser ident
    deriving Typeable

haskellからUserテーブルの中のpasswordにアクセスするときには,
xを対象のdbとしたとき,

userPassword x

と,テーブル名と要素名から決まるフィールド名を使ってアクセスします.
(ここもしばらくハマりました)

Foundation.hs中の関数,authenticateを少し書き換え

    authenticate creds = runDB $ do
        x <- getBy $ UniqueUser $ credsIdent creds
        case x of
            Just (Entity uid _) -> return $ Authenticated uid
            Nothing -> return Authenticated <$> insert User
                userIdent = credsIdent creds
                userPassword = Nothing

(初期はこのように,未登録ユーザは追加登録するようしていましたが,
誤入力を想定していなかったので,現在は(UserError InvalidLogin)を返すようにしています)

次に,入力フォームを作っていきます.
今回は,元からあるProfile画面をいじっていくことにします.
Profile.hsには,以下のように関数宣言されています

getProfileR :: Handler Html
getProfileR = do
    (_, user) <- requireAuthPair
    defaultLayout $ do
        setTitle . toHtml $ userIdent user <> "'s User page"
        $(widgetFile "profile")

私は最初ここでひっかかったのですが,

widgetFile $ "profile"

とすると,profile.(hamlet,lucius, julius,cacius)の全ファイルを参照するようです.
まずはprofile.hamletを見てみましょう.
hamletはhtml記法が使える上にhaskellの関数にもアクセス可能です.

<div .ui.container>

    <p class="username">
      hello, #{userIdent user}!

    <div>
      <input name="selectMenu" type="radio"value="A">Aランチ
      <input name="selectMenu" type="radio"value="B">Bランチ
      <input name="selectMenu" type="radio"value="C">Cランチ

    <div class="submit" name="submit">
      Submit!!

    <div id="paymentblock">
      今月の注文履歴
      <table class="personalpayment">
        <tr><td name="A">Aランチ</td>
        <tr><td name="B">Bランチ</td>
        <tr><td name="C">Cランチ</td>

profile.luciusはcss表記が使えるので,適当に書いて実行.

注文履歴がまだなかったり,スタイルが崩れていたりしますが,
体裁は後でどうにでもなるので置いておきます.

そして次は注文管理....のところで,力が尽きてしまったので,今回はここまで....
ごめんなさい.
yesodサンプルのファイル構造や,どこを弄れば良いかを簡潔にまとめた記事があまりなかったので,これが一助に慣ればと思います.
続きの記事は年末年始にでも更新します.

参考URL
https://qiita.com/YoungjaeKwon/items/8265834c0436944bc3ca