Step5

English page

Kahua Release

kahua-web Release

Security Advisory

Event Log

Documentation

For developers

Site info

Related Site

ソーシャルブックマークを作る(3)

ここまでで、フォームからブックマークを登録することと、登録したブックマー クを一覧することまではできるようになりました。しかし、問題がいくつもあり ます。

  1. 同じURLを何度も登録できてしまう
  2. 不正なデータ(URLが空文字列とか)も登録できてしまう

ここでは、まず最初の問題を解決することにしましょう。

インデックススロットとギャザリング

URLのuniquenessを保障する一番簡単な方法は、<bookmark> の url スロットに インデックススロットオプションをつけることでしょう。こんな具合に。

(define-class <bookmark> (<kahua-persistent-base>)
  ((title :allocation :persistent :init-keyword :title :init-value "")
   (url :allocation :persistent :init-keyword :url :init-value "" :index :unique)))

ただ、これだと同じURLを登録しようとした時、単にエラーになってしまいます。 せっかくなので同じURLを登録したら、そのURLへのvotingであるということに しましょう。voteされた数をカウントして保存しておけば、そのブックマークの 人気度を測れそうです。

(define-class <bookmark> (<kahua-persistent-base>)
  ((title :allocation :persistent :init-keyword :title :init-value "")
   (url :allocation :persistent :init-keyword :url :init-value "" :index :unique)
   (count :allocation :persistent :init-value 1)))

そして、ブックマークを登録する部分を、単に<bookmark>クラスのインスタンス を作るのではなく、既存のものがあったらカウンタをインクリメントするように 書き換えるわけです。そのための register-bookmark という手続きを書きましょう。

(define (register-bookmark title url)
  (or (and-let* ((bm (find-kahua-instance <bookmark> 'url url)))
        (inc! (ref bm 'count))
        bm)
      (make <bookmark> :title title :url url)))

ちょっと複雑ですが、読めばわかりますよね。find-kahua-instance は、条件に 当てはまるインスタンスを探すメソッドで、ここではインデックススロットの スロット名と値からインスタンスを探し、見つかればcountスロットの値を 破壊的にインクリメントし、見つからなければ新しい <bookmark> インスタンス を作っています。(and-let* ...)フォームの最後にbmを書いているのは、 単純に手続き register-bookmark 全体としてブックマークインスタンスを返す ように揃えるためです。

これで、手続き bookmark-form/ の中の <bookmark> のインスタンスをmakeしている ところを置き換えます。

(define (bookmark-form/ title url)
  (form/cont/
   (@@/ (cont (entry-lambda (:keyword title url)
                (register-bookmark title url)
                (redirect/cont (cont all)))))
   (table/
    (tr/ (th/ "Title: ") (td/ (textbox/ "title" title 50)))
    (tr/ (th/ "URL: ")   (td/ (textbox/ "url"   url   50)))
    (tr/ (th/) (td/ (submit/ "Register"))))))

それぞれ評価し、アプリケーションにブラウザからアクセスして試してみましょう。 同じURLを登録してみると、エラーになりませんが、一覧表示を見ると増えていないのが わかると思います。各インスタンスのcountスロットの値はインクリメントされている はずです。

一覧表示の改良

登録回数をカウントできるようになりましたが、カウントした値は表示したい ですよね。ということで、bookmark-entry/ にも手を入れて、カウントを表示する ようにしてみます。

(define (bookmark-entry/ bm)
  (li/ (a/ (@/ (href (slot-ref bm 'url))) (slot-ref bm 'title))
       "(" (slot-ref bm 'count) ")"))

ついでに、カウント値の大きい順にソートしてみましょうか。手を入れる のは、bookmark-list/ です。

(define (bookmark-list/ bm-collection)
  (define (count-cmp bm1 bm2)
    (> (slot-ref bm1 'count) (slot-ref bm2 'count)))
  (ul/ (map/ bookmark-entry/
             (sort (coerce-to <list> bm-collection) count-cmp ))))

何だか複雑に見えますが、ソートするための比較関数を内部定義して、ブックマークの コレクションをリストに変換してソートしているだけです。

それぞれ評価したら一覧表示を見てみてください。カウントの値が各ブックマークの後ろ に括弧に囲まれて表示され、カウントの大きいものから並んでいるのがわかると思います。

ここまででわかると思いますが、Kahuaでは永続クラスの定義の変更に既存のインスタンス が自動的に追随していきます。:index スロットオプションを指定すれば自動的にインデックス が作成され、スロットを追加すれば既存のインスタンスにも自動的に追加されます。 いちいちデータ変換を行う煩雑さがないことで、スムースにダイナミックプログラミングを 進めることができます。

さて、ここまで書いたら、make installして kahua-admin update bookmarks しておきま しょう。Kahuaでの開発は

  • コードを書く
  • すぐ評価する
  • 動作を確認する

というステップを繰り返し、ある程度まとまったところで、

  • make install
  • kahua-admin update <アプリケーション名>

を実行する。この繰り返しなのです。

最後に、現段階の bookmarks.kahua のコードを掲載しておきます。

(use gauche.collection)

(load "bookmarks/version.kahua")

(define page-template
  (kahua:make-xml-template
   (kahua-template-path "bookmarks/page.xml")))

(define (standard-page title body)
  (kahua:xml-template->sxml
   page-template
   :title (title/ (@/ (id "title")) title)
   :body (div/ (@/ (id "body")) (h1/ title) body)))

(define (make-link/ entry text)
  (p/ (a/cont/ (@@/ (cont entry)) text)))

(define-class <bookmark> (<kahua-persistent-base>)
  ((title :allocation :persistent :init-keyword :title :init-value "")
   (url :allocation :persistent :init-keyword :url :init-value "" :index :unique)
   (count :allocation :persistent :init-value 1)))

(define (bookmark-entry/ bm)
  (li/ (a/ (@/ (href (slot-ref bm 'url))) (slot-ref bm 'title))
       "(" (slot-ref bm 'count) ")"))

(define (bookmark-list/ bm-collection)
  (define (count-cmp bm1 bm2)
    (> (slot-ref bm1 'count) (slot-ref bm2 'count)))
  (ul/ (map/ bookmark-entry/
             (sort (coerce-to <list> bm-collection) count-cmp ))))

(define-entry (all)
  (standard-page "All Bookmarks"
                 (node-set/
                  (new-bookmark-link/)
                  (bookmark-list/ (make-kahua-collection <bookmark>))
                  (version-link/))))
(define all-bookmarks-link/ (cut make-link/ all "[All Bookmarks]"))

(define (textbox/ name value . maybe-size)
  (input/ (@/ (type "text") (name name) (value value)
              (size (get-optional maybe-size #f)))))
(define (submit/ value)
  (input/ (@/ (type "submit") (value value))))

(define (register-bookmark title url)
  (or (and-let* ((bm (find-kahua-instance <bookmark> 'url url)))
        (inc! (ref bm 'count))
        bm)
      (make <bookmark> :title title :url url)))

(define (bookmark-form/ title url)
  (form/cont/
   (@@/ (cont (entry-lambda (:keyword title url)
                (register-bookmark title url)
                (redirect/cont (cont all)))))
   (table/
    (tr/ (th/ "Title: ") (td/ (textbox/ "title" title 50)))
    (tr/ (th/ "URL: ")   (td/ (textbox/ "url"   url   50)))
    (tr/ (th/) (td/ (submit/ "Register"))))))

(define-entry (new)
  (standard-page "New Bookmark"
                 (node-set/
                  (all-bookmarks-link/)
                  (bookmark-form/ "" ""))))
(define new-bookmark-link/ (cut make-link/ new "[New Bookmark]"))

(define-entry (version)
  (standard-page "bookmarks version"
                 (node-set/
                  (h2/ *bookmarks-version*)
                  (all-bookmarks-link/))))
(define version-link/ (cut make-link/ version "[Version]"))

;
; initialization
;
   
(initialize-main-proc all)

Copyright (c) 2003-2007 Kahua Project Contact | About Us