継続を使ったコントローラを作るGaucheでメタプログラミング(5)(3/3 ページ)

» 2009年02月20日 00時00分 公開
[吉田裕美有限会社イーワイオフィス]
前のページへ 1|2|3       

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")
controller.scm

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")
orm.scm

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")
user.scm

テンプレート

 このアプリケーションでは、以下の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>
index.tmpl
<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>
edit.tmpl
<html>
  <head>
    <meta http-equiv="refresh" content="0;URL=/">
  </head>
  <body></body>
</html>
update.tmpl

まとめ

 今回は、Webアプリケーションのコントローラの部分を作ってみました。継続を使うことで、編集画面の表示し、データを受け取り、データベースを更新する処理の流れをスマートに記述できることを見てきました。

 また、Gaucheには正規表現や実用的なライブラリがあることで、手軽にWebアプリケーションが書けることも感じていただけたかと思います。

 なお、今回のcontrollerのコードは書籍「プログラミングGauche」第26章のコードを参考にしています。

 Gaucheには、Kahuaという継続ベースのWebアプリケーション用フレームワークがあります。また、継続ベースのWebアプリケーション用フレームワークとしてはSmalltalkで書かれたseasideが有名です。継続に興味を持たれた方はぜひ調べてみてください。

関連リンク:

Kahua
http://www.kahua.org/
seaside
http://www.seaside.st/


 今回作成したWebアプリケーションに必要な全ファイルをzipでまとめたファイルが、ここからダウンロードできます。

著者紹介

吉田 裕美

有限会社イーワイオフィス


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。