アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Lisp on Rails 第9回 〜 ビュー

こんにちは!! tahara です。 Objective-C づけになり、すっかりこぶさたしておりましたが Lisp on Rails 第9回です!

今回はビューです。 Common Lisp で ERB 相当を実装します。 Common Lisp で実装するからにはリードテーブルを使い、 HTML ファイルを関数にコンパイルしたいと思います。

HTML ファイルを関数にコンパイルソースは http://github.com/quek/lisp-on-rails/blob/master/action-pack/ecl.lisp です。

ところどころ説明させていただきます。 html-defun-readtable ではビューファイルの最初の1文字をマクロキャラクタにして、 先頭に (in-package :xxxx) を追加し、全体を (defun xxxx () ...) でくるむようにしています。 動的にリーダをカスタマイズしているのです。 これでビューファイルを1つの関数として読み込むことができるようになります。

(defun html-defun-readtable (fname pathspec)
  (let ((*readtable* (basic-readtable)))
    (set-macro-character
     (first-char pathspec)
     (let ((in-package t))
       (lambda (stream char)
         (unread-char char stream)
         (print
          (if in-package
              (progn
                (setf in-package nil)
                `(in-package ,(package-name action-controller:*app-package*)))
              `(defun ,fname ()
                 ,(body-code stream char)))))))
    *readtable*))

Rails ではコントローラからビューへの値の受け渡しは @foo のようなインスタンス変数が使われます。 それに対応するためビューファイルの中に @ で始まるシンボルがあれば、 コントローラのスロット値へのアクセスに変換するシンボルマクロを定義します。

(defun body-code (stream char)
  (walk-body-code (read-body-code stream char)))

(defun read-body-code (stream char)
  (let ((*readtable* (make-html-readtable char)))
    (loop for x = (read stream nil stream t)
          until (eq x stream)
          collect x)))

(defun walk-body-code (code)
  `(symbol-macrolet
       ,(series:collect
            (series:mapping
             ((x (series:choose-if (q:^ q:symbol-head-p _ "@")
                                   (series:scan-lists-of-lists-fringe code))))
             `(,x (slot-value action-controller:*controller*
                              ',(intern (subseq (symbol-name x) 1)
                                        action-controller:*app-package*)))))
     ,@code))

そんなこんなで、なんとかモデル、コントローラ、ビューが繋がりました。

モデル

(in-package :blog)

(def-record post
  (:has-many comments))

(def-record comment
  (:belongs-to post))

コントローラ。生の defclass です。

(in-package :blog)

(defclass top-controller (application-controller)
  ((message)
   (posts)))

(defmethod index ((self top-controller))
  (with-slots (message posts) self
    (setf message "まみむめも♪"
          posts (all post))))

ビュー。HTML タグと loop が混在するのもまた一興ですね。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ブログ</title>
  </head>
  <body>
    <h1><%= @message %></h1>
    <h2>投稿を loop で表示する</h2>
    <ul><% (loop for post in @posts for comments = (comments-of post) do %>
      <li><%= (content-of post) %> -- <%= (name-of post) %></li>
      <% if comments do %>
      <ul><% (loop for comment in comments do %>
        <li><%= (body-of comment) %> -- <%= (commenter-of comment) %></li><% ) %>
      </ul><% ) %>
    </ul>
  </body>
</html>

コントローラは次のように書けるようにすると、それっぽい気もしますが、いまはまだ書けません。

(def-controller top (application)
  (def-action index
      (setf @message "まみむめも♪"
            @posts (all post)))
  (def-action foo
      (setf @essage "foo")))

以上、まとめますと 「リーダをいじれる言語は素敵ですね!」 でした。

ソースはこちらから http://github.com/quek/lisp-on-rails

第10回につづきます。