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

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

Lisp on Rails 第7回 〜 ActiveRecord::Base の find メソッド

遅くなりました!! tahara です。 Lisp on Rails 第7回です!

今回は ActiveRecord::Base の find メソッドの機能を多少実装してみたいと思います。

ActiveRecord::Base の find メソッドは次の4つの使い方があります。

  1. id で検索。 引数は (id, *args), (id1, id2, ..., *args), ([id1, id2, ..., *args]) の3パターン。 該当するレコードがない場合は RecordNotFound が発生する。
  2. 最初の1件を検索。 引数は (:first, *args) で、該当がない場合は nil を返す。 Model.first(*args) というショートカットがある。
  3. 最後の1件を検索。 引数は (:last, *args) で、該当がない場合は nil を返す。 Model.last(*args) というショートカットがある。
  4. 該当する全件を検索。 引数は (:all, &args) で、該当がない場合は nil を返す。 Model.all(*args) というショートカットがある。

上記の4つの使い方全てで次のハッシュオプションが使えます。

  • :conditions - いわゆる検索条件。文字列またはリストで指定。
  • :order - SQL の ORDEY BY
  • :group - SQL の GROUP BY
  • :having - SQL の HAVING
  • :limit - 最大取得件数。
  • :offset - 取得開始位置。
  • :joins - SQL の JOIN だけど、普通次の :include を使う。
  • :include - 検索時に一緒にとってくるテーブルをアソシエーション名(has_many :xxxs)で指定する。
  • :select - 取得カラム。デフォルトは "*"。
  • :from - SQL の FROM。ビューから検索するとき等に使える。
  • :readonly - 取得結果をリードオンリー指定する。
  • :lock - SQL でのロック。"FOR UPDATE" とか。

このような場合 Common Lisp ではマルチディスパッチとキーワード引数を使えばうまくいくはずです。

;; find は CL にあるので select にする
(defgeneric select (class id-or-keyword
                          &key
                          conditions
                          order
                          group
                          having
                          limit
                          offset
                          joins
                          include
                          select
                          from
                          readonly
                          lock
                          &allow-other-keys))

(defmethod select ((class symbol) id-or-keyword &rest args)
  (apply #'select (find-class class) id-or-keyword args))

(defmethod select ((class active-record-class) (id integer)
                   &rest args
                   &key conditions)
  (setf conditions (append (list :id id) conditions))
  ...)

(defmethod select ((class active-record-class) (ids list)
                   &rest args
                   &key conditions)
  (setf conditions (append (list :id ids) conditions))
  ...)

(defmethod select ((class active-record-class) (keyword (eql :all))
                   &rest args)
  ...)

(defmethod select ((class active-record-class) (keyword (eql :first))
                   &rest args
                   &key (order "id"))
  ...)

(defmethod select ((class active-record-class) (keyword (eql :last))
                   &rest args
                   &key (order "id"))
  ...)

(defun all (&rest args)
  (apply #'select (car args) :all (cdr args)))
;; first と last は CL パッケージとかぶる。

といった感じで実装してみました。 joins, include, readonly 等はまだ未実装です。

しかし Common Lisp パッケージとシンボル名(find, first, last)がかぶるのが悩ましいところです。shadowing しようかしらん。

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

第8回につづきます。