著者:喜多川 豪 編集:かずひこ
Ruby で CGI を作成した事がある方は判るかと思いますが、Ruby には cgi.rb というライブラリが標準で利用する事が出来ます。 cgi.rb でコードの再利用やテンプレートとロジックの分離等を行うためには自前でライブラリを書いたりする必要があります。 また、バックエンドにデータベースを利用する場合、なるべくロジックからはデータベースを意識しないようなコードを書きたくなります。
これらの問題を解消する手段として、本記事では Web アプリケーションフレームワークの CGIKit、O/R マッピングツールの TapKit を利用した Web アプリケーションの作成方法を説明します。
CGIKit は Ruby で書かれた Web アプリケーションフレームワークです。
CGIKit による Web アプリケーションは、
というファイルで構成されるコンポーネントを組み合わせて作成します。 これらのファイルで記述されたエレメント (インスタンス変数やメソッドを HTML として表示する仕組み) をコードがバインディングし、HTML に変換して出力することで Web ページが表示されるようになっています。
Ruby 標準の cgi.rb で CGI を作成していると、コードやテンプレートの再利用が難しいのですが、CGIKit では Web ページのヘッダ部分 (サイトのタイトル等) やフッタ部分、またはどのページにも表示させたいパーツ等再利用したいものは全てコンポーネントとして分ける事で、コードの再利用を可能にし、開発効率を上げる事ができます。
また、CGIKit のコンポーネントは、デザインとロジックが分離されているので、ロジックに影響しないデザインだけであればコードを書き換える事無く変更する事ができます。
TapKit はオブジェクト-リレーショナルデータベース間のマッピングを行うフレームワークです。 細かい内容はるびま 4 号の記事「Ruby Library Report — [第3回] O/R マッピング」を参考にしてください。
では実際に CGIKit と TapKit を使って Web アプリケーションを作成してみましょう。 今回は簡易 BBS (掲示板) を作成してみます。 手順としては以下のようになります。
まず掲示板 (以下 BBS と呼ぶ) の操作の流れは今回以下のようにしてみます。
[スレッド一覧] ----> [スレッド新規登録]
|
+------------> [コメント一覧]
「スレッド一覧」画面ではスレッドの一覧を表示し、「スレッド新規登録」ではスレッドを新しく登録出来るようにします。「コメント一覧」ではスレッドに対してのコメントの一覧を表示し、またコメントを入力出来るようにします。
BBS の仕様は [4.Webアプリケーションを作ってみる] の通りですが、まずは CGIKit、TapKit のインストールから行いましょう。 今回 CGIKit は CVS から取得します。取得するものは安定版であるバージョン 1 系のものになります。
$ cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/cgikit login
パスワードを聞かれたら、Enter を押す。
$ cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/cgikit co cgikit-1
$ cd cgikit-1
$ ruby setup.rb config
$ ruby setup.rb setup
# ruby setup.rb install
うまくインストール出来たでしょうか? 次に TapKit をインストールします。 今回 bbs のデータを扱うためにデータベースを使用しますが、データベースは RDBMS の PostgreSQL を利用します。 TapKit では PostgreSQL を利用するために RAA:ruby-dbi、RAA:postgres を使いますのでこれらを先にインストールしておきましょう。 ruby-dbi、ruby-postgres がインストール出来ましたら TapKit も CVS から取得します。 インストール方法は CGIKit と同じように、
$ cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/tapkit login
パスワードを聞かれたら、Enter を押す。
$ cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/tapkit co tapkit
$ cd tapkit
$ ruby setup.rb config
$ ruby setup.rb setup
# ruby setup.rb install
となります。 これで準備は完了です。
CGIKit による Web アプリケーションは、[1. CGIKitとは] で説明したとおり、.html、.ckd、*.rb というファイル群で構成されます。これら三つのファイルを合わせてコンポーネントと呼び、画面毎にこのコンポーネントを作成していきます。 今回作成する BBS では、[4. Webアプリケーションを作ってみる] での操作の流れから、以下の三つのコンポーネントを作成します。
まず MainPage コンポーネントのテンプレート、バインディングから作成します。 CGIKit のテンプレートはバインディングファイルと対となっています。 エレメントは、
<cgikit name="*****">.....</cgikit>
や、</cgikit>を省略した形、
<cgikit name="*****" />
と記述します。 では MainPage のテンプレートを見てみましょう。
テンプレートファイル (MainPage/MainPage.html)
“edit” というエレメントがまず出て来ます。これはご想像のとおりスレッドを追加するページへのリンクになります。以下のバインディングファイルでエレメントの内容を見てみましょう。
バインディングファイルは、
定義名 : 種類 {
属性 = 値
属性 = 値
...
}
という書式で記述します。定義名はテンプレートで指定した名前が入り、種類は CGIKit で提供されているエレメントや他のコンポーネントを指定します。 { と } の間はそのエレメントの属性を定義します。 例えば、edit にあたるバインディングは以下のようになります。
edit : CKHyperlink {
page = 'EditPage'
}
これは、EditPage というコンポーネントへのリンクを生成するエレメントになります。
次に thread_list というエレメントが登場します。これにあたるバインディングは以下のようになります。
thread_list : CKRepetition {
list = thread_list
item = thread
index = index
}
これは、<cgikit> タグで囲んだ内容を繰り返すエレメントになります。 つまり、<cgikit name=”thread_list”> から </cgikit> までを繰り返すという意味になります。ここでは HTML での
<table>
<tr>
<td>2004-01-01</td>
<td>けいじばん</td>
<td>喜多川</td>
</tr>
.
.
.
</table>
のように表を 1 行ずつ繰り返す役割をしています。 thread_list の属性は、list、item、index が指定されてますが、
となっています。 つまりコード (*.rb) で thread_list へ配列で値を渡すと item 属性の thread へ 1 要素ずつ展開されるようになります。ここでの配列は、
[{'date' => '2004-01-01',
'title' => 'けいじばん',
'register' => '喜多川'
'query' => {'thread_no' => 1}
},
....]
のようにコードで要素にはハッシュで値を渡すようにしています。 なのでバインディングでは、以下のように date や title といった値として thread[‘date’] や thread[‘title’] などのように指定します。
また、以下のバインディングファイルで、CKString というエレメントが記述されていますが、CGIKit が提供している各エレメントには escape という属性がありますのでこれを利用する事で自前でエスケープ等をしなくて良くなります (escape 属性はデフォルトで有効)。
エレメントの一覧は CGIKit のドキュメント を参考にすると良いでしょう。
バインディングファイル (MainPage/MainPage.ckd)
コード (MainPage/MainPage.rb)
次に TapKit で利用するモデルファイルを作成しましょう。 TapKit ではマッピングの設定を記述したファイルをモデルファイルと呼び、このファイルには他にデータベースと接続するアダプタの情報も記述します。
まず今回作成する BBS のデータベースのテーブル構造は以下のようにします。
/* bbs */
/* base table */
create table thread (
thread_no integer, /* 0. スレッド No */
title text, /* 1. タイトル */
date text, /* 2. 登録日 */
register text, /* 3. 名前 */
e_mail text, /* 4. メールアドレス */
contents text, /* 5. 内容 */
primary key (
thread_no
)
);
/* comment table */
create table comment (
thread_no integer, /* 0. スレッド No */
comment_no integer, /* 1. コメント No */
date text, /* 2. 登録日 */
register text, /* 3. 名前 */
e_mail text, /* 4. メールアドレス */
contents text, /* 5. 内容 */
primary key (
thread_no,
comment_no
)
);
これを PostgreSQL に bbs という名前の DB として作成します。 次にデータベーススキーマからモデルファイルを自動生成します。 TapKit をインストールすると modeler というコマンドもインストールされますが、これが自動生成用のスクリプトになります。 モデルファイルの自動生成は以下のようにします。 (モデルファイルのファイル名は model.conf とします)
$ modeler PostgreSQL model.conf
とすると、以下のように対話的に設定を行う事が出来ます。
kida@sairen:~/src/bbs$ modeler PostgreSQL model.conf
Login database with DBI
URL: dbi:Pg:bbs
Username: postgres
Password:
Selectable tables - comment, pg_aggregate, pg_am, pg_amop,
pg_amproc, pg_attrdef, pg_attribute, pg_cast, pg_class,
pg_constraint, pg_conversion, pg_database, pg_depend,
pg_description, pg_group, pg_index, pg_inherits, pg_language,
pg_largeobject, pg_listener, pg_namespace, pg_opclass,
pg_operator, pg_proc, pg_rewrite, pg_shadow, pg_statistic,
pg_trigger, pg_type, sql_features, sql_implementation_info,
sql_languages, sql_packages, sql_sizing, sql_sizing_profiles, thread
(If you want to select the all tables, input 'all')
Select tables (separate table names with comma): thread,comment
Create model.conf
これでカレントディレクトリに model.conf というファイル名のモデルファイルが作成されます。 以下のモデルファイルは自動生成されたファイルを若干修正しています。 具体的には text 型のフィールドで width が-1 と設定されているのを削除したり、primary key を class_properties に追加したりしています。 また、次の章で説明しますが、class_name を GenerciRecord から BBS::Thread や BBS::Comment に変更しています。
---
adapter_name: PostgreSQL
connection:
password: ''
url: dbi:Pg:bbs
user: postgres
entities:
-
attributes:
-
allow_null: true
class_name: Integer
column_name: thread_no
external_type: integer
name: thread_no
read_only: false
-
allow_null: false
class_name: String
column_name: contents
external_type: text
name: contents
read_only: false
-
allow_null: false
class_name: String
column_name: date
external_type: text
name: date
read_only: false
-
allow_null: false
class_name: String
column_name: e_mail
external_type: text
name: e_mail
read_only: false
-
allow_null: false
class_name: String
column_name: register
external_type: text
name: register
read_only: false
-
allow_null: false
class_name: String
column_name: title
external_type: text
name: title
read_only: false
class_name: BBS::Thread
class_properties:
- contents
- e_mail
- register
- title
- comments
- date
- thread_no
external_name: thread
name: Thread
primary_key_attributes:
- thread_no
relationships:
-
delete_rule: nullify
destination: Comment
join_semantic: inner
joins:
-
destination: thread_no
source: thread_no
mandatory: false
name: comments
to_many: true
-
attributes:
-
allow_null: false
class_name: Integer
column_name: comment_no
external_type: integer
name: comment_no
read_only: false
-
allow_null: false
class_name: Integer
column_name: thread_no
external_type: integer
name: thread_no
read_only: false
-
allow_null: false
class_name: String
column_name: date
external_type: text
name: date
read_only: false
-
allow_null: false
class_name: String
column_name: e_mail
external_type: text
name: e_mail
read_only: false
-
allow_null: false
class_name: String
column_name: register
external_type: text
name: register
read_only: false
-
allow_null: false
class_name: String
column_name: contents
external_type: text
name: contents
read_only: false
class_name: BBS::Comment
class_properties:
- e_mail
- register
- thread
- date
- contents
- comment_no
- thread_no
external_name: comment
name: Comment
primary_key_attributes:
- comment_no
- thread_no
relationships:
-
delete_rule: nullify
destination: Thread
join_semantic: inner
joins:
-
destination: thread_no
source: thread_no
mandatory: false
name: thread
to_many: false
これで TapKit を利用する準備が整いました。
次に TapKit を利用してデータベースにアクセスするクラスを作成しましょう。
CGIKit で利用する*.rb の実装では以下のようにクラスにアクセスする事を想定して作成します。
def init
threads = Thread.list
@thread_list = Array.new
threads.each do |obj|
@thread_list.push({
'query' => {'thread_no' => obj.thread_no},
'date' => obj.date,
'title' => obj.title,
'register' => obj.register
})
end
end
上記の通り、作成するクラス名は Thread という名前のクラスになります。 Thread クラスを作成する前に、TapKit::GenericRecord という Tapkit 独自のクラスを継承するクラスを作成します。 このクラス (BBSRecord) ではモデルファイルから TapKit の Application オブジェクトを作成します。
class BBS
class BBSRecord < TapKit::GenericRecord
def self.connect(model)
@@tap = TapKit::Application.new(model)
end
end
end
BBSRecord クラスの connect メソッドは index.cgi というこの BBS アプリケーションでアクセスする cgi ファイルから呼ばれるようにします。
#!/usr/bin/env ruby
require 'cgikit'
require 'tapkit'
require 'lib/bbs.rb'
require 'lib/bbs_record'
require 'lib/thread'
require 'lib/comment'
BBS::BBSRecord.connect('sql/model.conf')
app = BBS.new
app.main = 'MainPage'
app.run
これで以下の Thread クラスの list メソッドで@@tap という Application オブジェクトを利用出来るようになります。
以下の list メソッドでは、Quqlifer.format でデータを取得するための条件を記述します。list メソッドでは全てのスレッドを取り出したいので空を指定しています。 次に、SortOrdering.new では thread_no というフィールドで降順にデータを取り出すという並び変えの設定を行っています。SQL でいう ORDER BY 句にあたります。 FetchSpec.new ではこれらの条件式、並び変えの設定を指定し、fetchspec.limit でデータの取得件数を指定します。 実際にデータを取り出すのは、@@tap.create_editing_context.fetch で取得します。
class BBS
class Thread < BBSRecord
def self.list(count = 20)
qualifier = Qualifier.format('')
sort = SortOrdering.new('thread_no', SortOrdering::DESC)
fetchspec = FetchSpec.new('Thread', qualifier, [sort])
fetchspec.limit = count
obj = @@tap.create_editing_context.fetch(fetchspec)
return obj
end
end
end
obj には以下のような Hash に似た値が帰ってきます。
[{thread_no = 2, contents = "内容", date = "2005-01-22",
e_mail = "kida@netlab.jp", register = "喜多川",
title ="開設しました",
comments = <FaultingDelayEvaluationArray -89021887>},
{thread_no = 1, contents = "内容 2", date = "2005-01-20",
e_mail = "kida@netlab.jp", register = "喜多川です",
title = "BBS ですよ",
comments = <FaultingDelayEvaluationArray -89021881>}]
実装では
obj[0].thread_no
obj[0].contents
等で値を取り出す事が出来ます。 comments は FaultingDelayEvaluationArray と記述されていますが、これは comment テーブルは thread と 1:多でリレーションシップの関係を持っているため、thread_no でリレーションされている comment テーブルのデータも取得出来ます。 上記の comments は、
obj[0].comments[0].contents
等でアクセス出来ますが、アクセスしない限り comment テーブルに対してデータベースにアクセスしません。これは TapKit のフォールティングの機能があるからです。
簡単ですが CGIKit と TapKit で Web アプリケーションを作成してみました。 コンポーネントの再利用や O/R マッピングの利便性等は説明しきれませんでしたが、CGIKit、TapKit に興味を持っていただけると幸いです。 今回製作した BBS のソースは bbs.tar.gz になります。 CGIKit はバージョン 2 系の開発も進んでおり、リリースされた時はまたレビューしてみたいと思っております。