アンケートなどの処理では過去に登録された値全てを累積的に 保存しておき、統計処理をしたい。ところがCGIでは、 CGIスクリプトが呼び出されて終了する間しか変数の値は 生きていない。つまり、<form>のあるWebページで入力された値は CGIスクリプトの終了とともに消えてしまう。入力名に入れられた値を残すためには、 そのときの変数などの状態をファイルに保存して、 次の起動時にそれを読み込むようにしなければならない。
Rubyでは、現在の変数の値をそのままの形でファイルに保存することが 容易にできる。これにはPStoreクラスを使う。
PStoreクラスを用いると数値、文字列、配列、ハッシュ、どんな値でも
直接ファイルに保存できる。文字列を保存することはこれまでも
open
で
できたのでPStoreで保存しても面白くない。ここではハッシュの値を
保存する例を示す。以下の例は変数x
を介して保存させる。
require 'pstore' x = PStore.new("値保存ファイル") x.transaction do x["foo"] = x["foo"] || Hash.new # x["foo"] が空なら新規ハッシュ代入 保存したい変数 = x["foo"] : : end
実際に動く短い例を見ていこう。
最初はCGIではなく普通のRubyスクリプトで、途中で設定した変数を 保存させるようにしていく例を見てみよう。「名前」と「一言」を読み込んで出 力するだけのプログラムを作ってみる。あとから複数の値のペアを追加すること を見越して、ハッシュを利用する。
#!/usr/koeki/bin/ruby
# coding: utf-8
word = Hash.new
print "名前は?: "
name = gets.chomp
print "ひとこと: "
word[name] = gets.chomp
for who, wd in word # または word.each do |who, wd|
printf("%sさんのひとこと「%s」\n", who, wd)
end
最後で、ハッシュに含まれるキーと値のペアを全て出力しているが、 プログラムを一度動かしても1つのキーと値しか登録されないので 何度実行しても1人の言葉しか出てこない。
% ./word.rb 名前は?: taro ひとこと: hoge taroさんのひとこと「hoge」 % ./word.rb 名前は?: hanako ひとこと: やほー hanakoさんのひとこと「やほー」 % ./word.rb 名前は?: John ひとこと: Hey, Jude Johnさんのひとこと「Hey, Jude」
これをPStoreを使って、そのときのハッシュ全体の値を別ファイル
(以下の例ではword.db
)に保存して毎回読み込むようにする。
#!/usr/koeki/bin/ruby
# coding: utf-8
require "pstore"
x = PStore.new("word.db")
x.transaction do
x["word"] = x["word"] || Hash.new # x["word"] ||= Hash.new でも可
word = x["word"]
print "名前は?: "
name = gets.chomp
print "ひとこと: "
word[name] = gets.chomp
for who, wd in word
printf("%sさんのひとこと「%s」\n", who, wd)
end
end
このようにすると、以前に起動された値を word.db
ファイル
に自動的に保存し、前回の値を引き継ぐことができる。
% ./word.rb 名前は?: taro ひとこと: hoge taroさんのひとこと「hoge」 % ./word.rb 名前は?: hanako ひとこと: やほー hanakoさんのひとこと「やほー」 taroさんのひとこと「hoge」 % ./word.rb 名前は?: John ひとこと: Hey, Jude Johnさんのひとこと「Hey, Jude」 hanakoさんのひとこと「やほー」 taroさんのひとこと「hoge」
word-pstore.rb
をCGIにしたものを示す。
次節で述べるが、このCGIを保存するディレクトリは別に作成する
必要がある。
#!/usr/bin/env ruby
# coding: utf-8
myname="word-pstore-cgi.rb"
require "cgi"
c = CGI.new(:accept_charset => "UTF-8")
require "pstore"
x = PStore.new("data/word.db") # 別ディレクトリにする
print "Content-type: text/html; charset=UTF-8\n\n"
print "<!DOCTYPE html>
<html>
<head><title>Word</title></head>
<body>"
# 値入力フォームもこのCGIで出力する。
# formのactionをこのCGIプログラムに指定している。
# (mynameはこのスクリプト名)
printf("<form method=\"POST\" action=\"./%s\">\n", myname)
print ' <p>
おなまえ: <input name="name" maxlength="40"><br>
ひとこと: <input name="word" maxlength="80"><br>
<input type="submit" value="GO">
<input type="reset" value="reset">
</p></form>'
x.transaction do
x["word"] ||= Hash.new
word = x["word"]
if c["name"] > "" && c["word"] > ""
name = c["name"]
word[name] = c["word"]
end
print "<pre>\n"
for p, w in word
# フォーム入力値を出力するときは必ず CGI.escapeHTML() する
person = CGI.escapeHTML(p)
wrd = CGI.escapeHTML(w)
printf("%sさんのひとこと「%s」\n", person, wrd)
end
print "</pre>"
end
puts "</body></html>"
CGIプログラムは自分のユーザ権限で動かない。Webサーバプログラムは 違うユーザ権限で動いているため、自分の保有するディレクトリに書き込むこと がそのままではできない。CGIプログラムにデータファイルを書かせるためには 当該ディレクトリを他人でも書き込めるように設定しておかなければならない。
データを書き込むディレクトリを別途作成する。作成中の CGI
プログラムが ~/public_html/mycgi
であると仮定するとその中に data/ ディレクトリを作成する。
mkdir ~/public_html/mycgi/data chmod 1777 ~/public_html/mycgi/data
chmod
の 1777 は、
誰にでも書き込みできるが既存ファイルを消すのはファイル所有者のみ、
という属性をもつディレクトリに設定することを意味する。
~/public_html/mycgi/
に
word-pstore-cgi.rb
を保存し chmod +x
しておく。
実際に動かす例を word-pstore-cgi に示す。
PStoreクラスを利用して、Webで入力できる自分用のデータベースを作ろう。
ここでは、何かの飲み物を飲んだときの感想を登録できるデータベースを 作る。入力名として、
飲み物の名前 | item |
感想 | comment |
を利用する。また、入力フォームの出力と、 現在登録されているデータの出力を両方とも行なうようなCGIプログラムとする。 大まかな構成は以下のようになる。
これをプログラム化した例が
cancoffee.rb
である。
#!/usr/bin/env ruby
# coding: utf-8
require "cgi"
require "pstore"
myname = "cancoffee.rb"
c = CGI.new(:accept_charset => "UTF-8")
item = c["item"]
cmt = c["comment"]
time = Time.now # 時刻を保持するTimeクラス代入。nowは現時刻
print 'Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head><title>飲んだものメモ</title>
<link rel="stylesheet" type="text/css" href="simple.css">
</head>
<body>
<h1>飲み物メモ</h1>
' # HTTPヘッダと冒頭部分
db = PStore.new("data/coffee.db")
db.transaction do # PStoreは db.transaction do ... end で使う
db["root"] ||= Hash.new
data = db["root"] # ここまではおきまり
if item >"" && cmt > "" # 名前とコメント、両方値があるなら登録
data[item] = [time, cmt] # 今日の日付とコメント
end
# フォーム出力
printf("<form method=\"POST\" action=\"./%s\">\n", myname)
print '<p>
飲んだもの: <input name="item" type="text" maxlength="40"><br>
コメント <br>
<textarea name="comment" cols="40" rows="5">
</textarea><br>
<input type="submit" value="OK">
<input type="reset" value="reset"><br>
</p><hr>'
# 既存のコメント出力(キー毎)
print "<dl>\n" # 定義環境開始
for i in data.keys.sort{|x, y|
data[y][0] <=> data[x][0] # 日付の新しい順にソート
}
day = data[i][0] # 第0要素が日付
msg = data[i][1] # 第1要素がコメント、それぞれ取り出す
printf(" <dt>%s</dt>\n", i) # キー(つまり飲んだものの名前)
printf(" <dd>記載日: %s<br>\n", day.strftime("%Y年%m月%d日"))
printf(" %s</dd>\n", CGI.escapeHTML(msg))
end
print "</dl>\n" # 定義環境終了
end # db.transaction 終わり
print "</form><hr></body>\n</html>"
実際に登録してみよう。
cancoffee.rb
では既存項目を入力すると、古い項目のコメントが消され、新しく入力されたコ
メントに置き換えられる。これにより後で修正することは可能だが、
入力ミスしたものなどを直す場合、
既存の項目の値を初期値として代入した状態
で入力フォームが出力されていると
都合がよい。そのように直したものを
cc2.rb
に示す。
#!/usr/bin/env ruby
# coding: utf-8
require "cgi"
require "kconv" # utf-8への変換のため(toutf8メソッド)
require "pstore"
myname = "cc2.rb"
c = CGI.new(:accept_charset => "UTF-8")
item = c["item"]
cmt = c["comment"]
time = Time.now # 時刻を保持するTimeクラス代入。nowは現時刻
edit_item = ARGV[0].to_s.toutf8 # 修正モードの場合の修正項目名
oldcomment = "" # 修正モードの場合の既存コメント
print 'Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head><title>飲んだものメモ</title>
<link rel="stylesheet" type="text/css" href="simple.css">
</head>
<body>
<h1>飲み物メモ</h1>
' # HTTPヘッダ
db = PStore.new("data/coffee.db")
db.transaction do # PStoreは db.transaction do ... end で使う
db["root"] ||= Hash.new
data = db["root"] # ここまではおきまり
if item >"" && cmt > "" # 名前とコメント、両方値があるなら登録
data[item] = [time, cmt] # 今日の日付とコメント
end
if edit_item > "" && data[edit_item]
oldcomment = data[edit_item][1]
end
# フォーム出力
printf("<form method=\"POST\" action=\"./%s\">\n", myname)
print '<p>
飲んだもの: <input name="item" type="text" value="' +
edit_item + '" maxlength="40"><br>
コメント <br>
<textarea name="comment" cols="40" rows="5">' + oldcomment + '
</textarea><br>
<input type="submit" value="OK">
<input type="reset" value="reset"><br>
</p><hr>'
# 既存のコメント出力(キー毎)
print "<dl>\n" # 定義環境開始
for i in data.keys.sort{|x, y|
data[y][0] <=> data[x][0] # 日付の新しい順にソート
}
day = data[i][0] # 第0要素が日付
msg = data[i][1] # 第1要素がコメント、それぞれ取り出す
# <a href="./cc2.rb?ITEM">ITEM</a> を出力
printf(" <dt><a href=\"%s?%s\">%s</a></dt>\n", myname, i, i)
printf(" <dd>記載日: %s<br>\n", day.strftime("%Y年%m月%d日"))
printf(" %s</dd>\n", CGI.escapeHTML(msg))
end
print "</dl>\n" # 定義環境終了
end # db.transaction 終わり
print "</form><hr></body>\n</html>"