継続を使ったコントローラを作る:Gaucheでメタプログラミング(5)(3/3 ページ)
Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう(編集部)
controllerモジュール
このcontrollerは継続すべき処理をハッシュに格納しています。ハッシュのキー、継続IDは十分に大きな乱数で重ならないような値にしています。リクエストにパラメタ名@cont@の継続IDが含まれる場合は、ハッシュに格納されている対応する継続処理を実行します。
継続IDがない場合は、リクエストのパスでedit、updateなどの処理へ分岐します。また、継続処理を簡潔に記述できるように、define-continuationマクロを定義しています。
edit関数で継続を使っていますが、編集用データを読み出した後、編集データを受け取りデータベースに格納する部分へと処理がつながっていくのが分かりやすいと思います。
また、通常のWebアプリケーションでは、player変数の値はセッションを使ってupdateに渡すのですが、そのようなコードはなく、普通のローカル変数として利用できるのがスマートだと思います。
(define-module controller
(use srfi-13)
(use srfi-27)
(use rfc.uri)
(use www.cgi)
(use orm)
(use user)
(use template)
(export dispatch))
(select-module controller)
(define *conts* (make-hash-table 'eqv?))
(define *max-cid* (expt 2 64))
(define (get-cont params)
(hash-table-get *conts*
(cgi-get-parameter "@cont@" params :convert string->number)
#f))
(define (push-cont! cont)
(let1 cid (random-integer *max-cid*)
(cond ((hash-table-get *conts* cid #f) (push-cont! cont))
(else (hash-table-put! *conts* cid cont) cid))))
(define-macro (define-continuation args . body)
`(push-cont! (lambda ,args ,@body)))
(define (dispatch request)
(receive (authority path query fragment) (uri-decompose-hierarchical request)
(let1 params (cgi-parse-parameters :query-string (or query ""))
(cond ((get-cont params) => (lambda(c) (c params)))
((string= path "/edit") (edit params))
((string= path "/update") (update params))
(else (index))))))
(define (index)
(let1 players (all <player>)
(render "index.tmpl" players)))
(define (edit params)
(let* ((player (find <player>
(cgi-get-parameter "id" params :convert string->number)))
(cid (define-continuation (params)
(set-params player params)
(save player)
(render "update.tmpl"))))
(render "edit.tmpl" player cid)))
(provide "controller")
ORMモジュール
第3回で作ったORM(ORマッパー)もモジュール化しました。また、リクエストパラメータの値でORMインスタンスを更新するset-paramsメソッドを追加しました。ちなみに、リクエストパラメータは、((属性名1 値1) (属性名2 値2) (属性名3 値3) ...)のようなリストです。
(define-module orm
(use dbi)
(use gauche.collection)
(use gauche.interactive)
(export <orm> <orm-meta> make all find save set-params))
(select-module orm)
(define *data-source-name* "dbi:sqlite3:alih.db")
(define-class <orm-meta> (<class>) ())
<中略>
(define-method first-element ((coll <collection>))
(find (lambda(el) #t) coll))
(define-method set-params ((object <orm>) params)
(for-each
(lambda(p)
(let1 col (string->symbol (car p))
(if (slot-exists? object col)
(slot-set! object col (cadr p)))))
params))
<中略>
(define (db-insert db-conn table-name slot-hash)
(let* ((places (map (lambda(c) "?") (hash-table-keys slot-hash)))
(sql #`"INSERT INTO ,table-name (,(string-join (hash-table-keys slot-hash) \",\")) VALUES (,(string-join places \",\"))")
(query (dbi-prepare db-conn sql)))
(apply dbi-execute (cons query (hash-table-values slot-hash)))))
(provide "orm")
userモジュール
userモジュールでは、<player>クラスを定義しています。ところで、ORMモジュールの中でevalを使ってクラスを再定義していますが、再定義されたクラスは、どのモジュールに定義されるのでしょうか。答えはGaucheユーザリファレンス: 6.18 evalとreplにあるように、evalの第3引数に(interaction-environment)を指定した場合はuserモジュールに定義されます。そのため<player>クラスは、userモジュール内に定義しています。
define-module user (use orm) (export <player>)) (select-module user) (define-class <player> (<orm>) ()) (provide "user")
テンプレート
このアプリケーションでは、以下の3つのテンプレートを使っています。
- index.html:一覧ページ
- edit.html:編集データ表示ページ
- update.html:編集終了後に一覧ページにリダイレクトするためのページ。今回のWebサーバは、redirect(ステータスコード 302)を戻せないので、このような方法でリダイレクトしています
<html>
<body>
<h2>Asia League Ice Hockey</h2>
<table border="1">
<% (for-each (lambda (player) %>
<tr>
<td><%= (no-of player) %></td>
<td><%= (name-of player) %></td>
<td><%= (g-of player) %></td>
</tr>
<% ) players) %>
</table>
</body>
</html>
<html>
<body>
<h2>Edit</h2>
<form action="update">
<input type="hidden" name="@cont@" value="<%= cid %>">
<table border="0">
<tr>
<td>No: </td>
<td><input type="text" name="no" value="<%= (no-of player)%>"></td>
</tr>
<tr>
<td>Name: </td>
<td><input type="text" name="name" value="<%= (name-of player)%>"></td>
</tr>
<tr>
<td>Goal: </td>
<td><input type="text" name="g" value="<%= (g-of player)%>"></td>
</tr>
<% ) %>
</table>
<input type="submit" value="Update">
</form>
</body>
</html>
<html>
<head>
<meta http-equiv="refresh" content="0;URL=/">
</head>
<body></body>
</html>
まとめ
今回は、Webアプリケーションのコントローラの部分を作ってみました。継続を使うことで、編集画面の表示し、データを受け取り、データベースを更新する処理の流れをスマートに記述できることを見てきました。
また、Gaucheには正規表現や実用的なライブラリがあることで、手軽にWebアプリケーションが書けることも感じていただけたかと思います。
なお、今回のcontrollerのコードは書籍「プログラミングGauche」第26章のコードを参考にしています。
Gaucheには、Kahuaという継続ベースのWebアプリケーション用フレームワークがあります。また、継続ベースのWebアプリケーション用フレームワークとしてはSmalltalkで書かれたseasideが有名です。継続に興味を持たれた方はぜひ調べてみてください。
今回作成したWebアプリケーションに必要な全ファイルをzipでまとめたファイルが、ここからダウンロードできます。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- いまさらアルゴリズムを学ぶ意味
コーディングに役立つ! アルゴリズムの基本(1) コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう - Zope 3の魅力に迫る
Zope 3とは何ぞや?(1) Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか? - 貧弱環境プログラミングのススメ
柴田 淳のコーディング天国 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く? - Haskellプログラミングの楽しみ方
のんびりHaskell(1) 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう - ちょっと変わったLisp入門
Gaucheでメタプログラミング(1) Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう