連載
» 2009年01月16日 00時00分 公開

GaucheでRDBプログラミングGaucheでメタプログラミング(3)(4/5 ページ)

[吉田裕美,有限会社イーワイオフィス]

ORマッパーの仕様

 今回作るORマッパーは、Ruby on RailsのORマッパーであるActiveRecord風のものにします。

 ActiveRecordは、

  • テーブルと対応したモデルクラスをActiveRecordのサブクラスとして定義する
  • モデルクラスの名前はテーブル名を単数形にしたものでテーブルとクラス名を関連付ける
  • モデルクラスはActiveRecordクラスに定義されたRDB操作用のクラスメソッドやインスタンスメソッドが使えるようになる
  • モデルクラスにはテーブルの全カラムに相当するインスタンス変数が自動的に作成され参照・設定できるようになる

という特徴があります。

 さて、今回のORマッパー「orm.scm」の仕様をgosh-rlなどで実際に使いながら説明します。

 準備として、SQLite3のデータベースファイル「alih.db」があるディレクトリでgosh-rlを起動し、orm.scmファイルに書かれたコードを読み込みます。また、作業用に広域変数pを定義しておきます。

gosh> (load "./orm.scm")
#t
gosh> (define p #f)
p

 ActiveRecord同様に<orm>クラスを継承したモデルクラスを定義します。クラス名はテーブル名を単数形にし、習慣どおり<>でくくった<player>です。親クラスとして<orm>を指定します。スロット(インスタンス変数)は特に定義しません。

gosh> (define-class <player> (<orm>) ())
#<<player>

 IDを指定して1レコードを取得するfindメソッド(クラスメソッド)を実行してみます。<player>クラスのインスタンスにはカラムに相当するスロットが追加され、ID = 1のレコードが取得されていることが分かります。

gosh> (set! p (find <player> 1))
#<<player> 0xc00498>
gosh> (d p)
#<<player> 0xc00498> is an instance of class <player>
slots:
  id        : 1
  no        : 20
  name      : "KATAYAMA,Kazuhito"
  pos       : "G"
  gp        : 3
  pts       : 0
  g         : 5
  a         : 0
  s         : 0
  sg        : 0
  pm        : "9/4"
  pim       : 0
  mp        : 0
  gm        : 0
  initialized: #t
  db-conn   : #<<sqlite3-connection> 0xc4e0f8>
  table-name: "players"
  column-names: ("id" "no" "name" "pos" "gp" "pts" "g" "a" "s" "sg" "pm" "pi

 インスタンス変数への参照メソッドが「カラム名-of」という名前で定義されているので、それを使ってインスタンス変数への参照や代入が行えます。

gosh> (name-of p)
"KATAYAMA,Kazuhito"
gosh> (no-of p)
20
gosh> (set! (g-of p) 6)
#<undef>
gosh> (d p)
#<<player> 0xc00498> is an instance of class <player>
slots:
  id        : 1
  no        : 20
  name      : "KATAYAMA,Kazuhito"
  pos       : "G"
  gp        : 3
  pts       : 0
  g         : 6 
(以下省略)

 変更されたインスタンスに対し、saveメソッド(これはインスタンスメソッド)を適応することで、インスタンスの値がDBに保存(この場合はUPDATE)されます。

gosh> (save p)
#t

ORマッパーのコア部分

 これからorm.scmのコードを説明していきます。コード全体はここからダウンロードできますが、ここでは分割して少しずつ説明していきます。まずはコアの部分。この部分は<orm>クラスの定義と<orm>を継承したモデルクラスにカラムに相当するスロットを動的に追加する部分です。

先頭

 参照するモジュールと定数の定義です。

(use dbi)
(use gauche.collection)
(define *data-source-name* "dbi:sqlite3:alih.db")

メタクラスの定義

 <class>を親クラスとして定義したクラスはメタクラスになります。習慣的にクラス名には<……-meta>とします。今回のメタクラスはスロットはありません。

(define-class <orm-meta> (<class>) ())

<orm>クラスの定義

 初期化済み、RDB接続、テーブル名、カラム名用のスロットを定義します。スロット定義の:allocationオプションはスロットの形態を表すもので、:classの場合はクラス変数になり継承したクラスを含め共用のスロットになります。また:each-subclassもクラス変数ですが、継承したクラスごとに独自になります。

 :metaclassオプションでメタクラスを指定しています。

(define-class <orm> ()
  ((initialized  :allocation :each-subclass)  ; 初期化済み(カラム情報取得済み)
   (db-conn :allocation :class)               ; RDB接続
   (table-name :allocation :each-subclass)    ; テーブル名
   (column-names :allocation :each-subclass)) ; カラム名のベクター
  :metaclass <orm-meta>)

 RDB接続が<orm>を継承した全モデルクラスで1つなのは、実用的なコードを書く際には再考しないといけませんね。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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