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

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

Lisp on Rails 第8回 〜 before_*

こんにちは!! tahara です。 Lisp on Rails 第8回です!

今回は ActiveRecord::Base の save, create, update, destroy 等々のメソッドには beforo_* や after_* というフックメソッドを定義することができます。 ActiveRecord::Callbacks でそのあたりの実装がされています。

これを Common Lisp でやろうとした場合、

(defmethod save :before ((self post)) ...)

で OK と思ったらそうはいきません。 before_* メソッドが false を返した場合はメソッドを呼び出しを中断する必要があります。 Common Lisp の before メソッドは返り値は無視してしまうので、そのまま使うことはできないのです。

仕方ないので自分で新しいメソッドコンビネーションを実装します。

(define-method-combination active-record ()
  ((around (:around))
   (before (:before))
   (primary () :required t)
   (after (:after)))
  "before メソッドが nil を返した場合メソッドの実行を中断する。"
  (flet ((call-methods (methods)
           (mapcar #'(lambda (method)
                       `(call-method ,method))
                   methods))
         (call-methods-and (methods)
           `(and ,@(mapcar #'(lambda (method)
                               `(call-method ,method))
                           methods))))
    (let ((form (if (or before after (rest primary))
                    `(when ,(call-methods-and before)
                       (multiple-value-prog1
                           (call-method ,(first primary)
                                        ,(rest primary))
                         ,@(call-methods (reverse after))))
                    `(call-method ,(first primary)))))
      (if around
          `(call-method ,(first around)
                        (,@(rest around)
                           (make-method ,form)))
          form))))

あとは defgeneric するときにこのメソッドコンビネーションを指定すれば OK です。

(defgeneric save (record)
  (:method-combination active-record)
  ...)

簡単にメソッドの呼び出し方法を定義できてしまうなんて Common Lisp はいい言語ですね。

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

第9回につづきます。