MoonWolf (moonwolf@moonwolf.com)
2004 年 11 月 9 日
そろそろ多機能なアプリケーションを作りたくなってきたでしょう? 多機能なアプリケーションは複数の画面を持ち、画面の遷移を必要とします。今回はそれらを綺麗にまとめ上げるためのアプリケーションフレームワークについて紹介します。
Blog、掲示板、Wiki など多機能なWebアプリケーションは入力画面、設定画面、結果表示画面などの複数の画面を持ち、それらの画面の間をリンク、フォームなどで遷移します。
CGI で複数の画面を実現する最も単純な方法は、各画面毎に CGI ファイルを作成していく方法です。eruby、ERB もこの方法に含まれます。 この方法は最初はうまくいくのですが、画面が増えてくると CGI ファイルの中で機能が重複したり画面の遷移が複雑化してメンテナンスのコストが急激に増えていきます。
Nora のアプリケーションフレームワークでは CGI ファイルを一つのクラスにまとめ、各画面をメソッドとして定義することでコードの重複を減らし、複雑な画面遷移を単純化します。
Perl の CGI::Application を参考に作られたシンプルなアプリケーションフレームワークです。 アプリケーションを実行モード (Run-Mode) の集まりとして扱い、共通のインターフェースを持つメソッドとしてまとめます。アプリケーションは Web とのやりとりを集中的に管理するコントローラと呼ばれるクラスで構成されます。
使い方としてはまず Web::Controller::Simple を継承したコントローラクラスを作成して setup、teardown メソッドを記述します。
require 'web'
require 'web/controller/simple'
class MyApp < Web::Controller::Simple
def setup(opt={})
# インスタンス変数の初期化、データベース接続などを行う
# @dbh = DBI.connect(...
end
def teardown
# アプリケーションの後始末。データベース接続の切断などを行う
# @dbh.disconnect
end
end
そして、実行モード (=画面) に対応したメソッドを定義します。 メソッドは ‘do_’ + 実行モード という名前で定義するきまりになっています。 実行モードを明示的に指定しない場合はデフォルトの実行モード ‘start’ が選択されるので、まず ‘do_start’ を定義します。
class MyApp < Web::Controller::Simple
def do_start(req,rsp)
rsp.content_type = 'text/plain'
rsp.write req['text1']
end
end
メソッドにはリクエスト (Web::Request) とレスポンス (Web::Response) の 2 つのオブジェクトが渡されます。 第 1 回、第 2 回 でやったようにリクエストからデータを取り出してレスポンスに出力します。 メソッドの戻り値は無視されるので何も考えないで良くなっています。
実行する CGI ファイルはコントローラの初期化と API とのやりとりを書くだけと非常にシンプルになります。
#!/usr/bin/env ruby
require 'web'
require 'simple'
ctrl = MyApp.new
ctrl.setup
api = Web::Interface::AUTO.new
api.each {|req|
rsp = ctrl.run(req)
api.response req, rsp
}
ctrl.teardown
次を指定して複数の画面を遷移してみましょう。 実行モードは CGI の ‘rm’ (run mode) というパラメータで指定します。 リンクを使って遷移する場合、クエリとして ‘?rm=edit’ とすることでコントローラの do_edit メソッドが起動され、実行モード edit の画面が表示されます。
<a href="?rm=edit;page=foo">fooの編集</a>
do_edit メソッドではパラメタを取得して画面を組み立てます。一般的には 前回 使った Web::Template でテンプレートを使って画面出力します。
class MyApp < Web::Controller::Simple
def do_edit(req,rsp)
page = req['page']
param = {
'page' => page
}
tmpl = Web::Template.new('filename'=>'edit.html')
tmpl.param = param
tmpl.output(rsp)
rsp.content_type = 'text/html; charset=UTF-8'
end
end
フォームを使う場合には実行モードは隠しフィールドを使って指定します。
<form method="post">
<input type="hidden" name="rm" value="save">
<input type="text" name="title" value="タイトル">
<textarea name="body"></textarea>
<input type="submit">
</form>
Wiki や日記でよくみられる「プレビュー」「保存」など、ボタンによって処理を変えたい場合があります。その場合は隠しフィールドではなくボタンの name 属性に ‘rm_’ で始まる名前を付けることで対応します。
<input type="submit" name="rm_preview" value="プレビュー">
<input type="submit" name="rm_save" value="保存">
このようにするとクリックしたボタンによって do_preview,do_save ボタンが起動されます。
保存した後に、通常の表示画面を表示したいなど、リダイレクトが必要になることがあります。 Location ヘッダを出力したり、<meta http-equiv=”Refresh” content=”0;URL=http://example.com/“>のような HTML を出力するなどの方法がありますが、Web::Controller::Simple ではコントローラ内部でのリダイレクトが使えます。
たとえば xxx から yyy にリダイレクトしたい場合、do_xxx メソッドで保存処理など必要な処理が終わった後、リクエストオブジェクトをリダイレクト後画面用に変更して do_yyy メソッドを起動します。
class MyApp < Web::Controller::Simple
def do_xxx(req,rsp)
page = req['page']
open(page,'w') {|f|
f.write req['body']
}
req['page'] = 'Top'
do_yyy(req,rsp)
end
def do_yyy(req,rsp)
page = req['page']
# ...
end
end
今回紹介した内容で Wiki や Blog、掲示板といった簡単な画面遷移のアプリケーションであれば組めるようになったと思います。 次回は複雑な画面遷移のためのセッション管理について紹介します。
MoonWolf は半導体メーカに勤めるプログラマです。2000 年に Ruby に触れ、それ以降 RAA (Ruby Application Archive) にてライブラリ・アプリケーションを発表し続けています。登録プロジェクト数 41 と世界 1 位です。今後も 1 位を維持するため日夜ライブラリの書けそうなネタを探しています。Ruby 関連の記事の執筆もしますので出版関係者のかた連絡お願いします。著者の連絡先は moonwolf@moonwolf.com です。