- 2007年9月 4日 11:58
- Ruby
前回のRubyでCGI.フォームデータを受けとるで勉強したRubyをCGIで動かしなおかつ入力したデータを受け取る方法をさらに活用すべくなにが良いかなーと考えたら、やっぱり入力したら保存でしょう。そうでしょう。記録は大切です。というわけで、掲示板を作ることにしました。
掲示板と言いつつ、管理機能やレスなどを実装したら身が持たないので最低限の機能のみの実装。入力→ログに書き込み→表示のそれだけしかできない簡易掲示板。そういえば、この掲示板をコーディングしているとき、Perlで同じような機能の掲示板を組んだことを思い出して非常に楽しかった。あれが初めてのプログラミングだった。
というわけで、今回はRubyで簡易掲示板を作ってみます。
掲示板の仕様
- HTMLタグは使用不可
- 全角25文字まで入力受付
- 最大5件ログを保存
- 投稿順に表示
最初は、なんの制限も付けないファイルの読み書きに絞ったコードにしようかと思ったけれど、そういった安易な解説がセキュリティホール満載のスクリプトを生んでしまいそうなので、なるべく安全な方向を目指しつつ、簡単に作れそうな仕様にしました。
必要な機能
さて、上記の仕様を実現するために必要な機能とはなんなのか…
- フォームからの入力データ取得
- 入力データの文字数取得
- 文字数によって、書き込み or エラー表示
- ログファイルへの書き込み
- 最新の投稿はファイルの末尾に書き込む
- ログファイルに6件目の書き込みが会った場合、古い投稿を消す
- ログファイルからの読み込み
- 読み込んだ際にHTMLタグをエスケープ
大まかにこんな感じの機能を実装すれば大丈夫っぽい。
入力データの文字数取得
フォームからの入力データ取得は前回やったので割愛。文字列の文字数を取得するには、.size を使えばOK。
strData = 'あいうえお' print strData.size #10 strData = 'abcdefg' print strData.size #7
なお全角文字の場合は、2byteなので見た目が5文字でも 10 と表示される。また、目に見えない改行コードやタブなども当然カウントされる。
文字数によって、書き込み or エラー表示
これは if を使えば非常に簡単。とはいえ、PHPやPerlとは文法が違うのでまだ戸惑っているけど。
#最大文字数を設定
MAXLEN = 10
strData = 'あいうえお'
if strData.size <= 10 then
print strData
else
print "最大文字数 #{MAXLEN} を超えてます"
end
ログファイルへの書き込み
PerlやPHPなどプログラマにはお馴染みの方式。
#ログファイル名 FILENAME = "01.dat" writedata = 'あいうえお' fp = open(FILENAME,"w") fp.print writedata fp.close
open(file,mode) を使ってファイルを各モードで開いて処理をしてクローズする。シンプル。
- "r"
- 読み込みモード
- "r+"
- 読み書き両用モード(ポインタはファイルの先頭にセット)
- "w"
- 新規作成書き込みモード
- "w+"
- 新規作成読み書き両用モード(オープン時にファイルを空にする)
- "a"
- 追加書き込みモード(ポインタはファイル末尾にセット)
- "a+"
- 追加書き込み読み書き両用モード(ポインタはファイルの先頭にセット)
上記の "w" だと全部上書きで書き込んでしまうので「最新の投稿はファイルの末尾に書き込む」ようにするには "a" を使う。
ログファイルに6件目の書き込みが会った場合、古い投稿を消す
今回の中で一番面倒くさいのがこの処理。最大5件までの投稿しかログに保存しない仕様なので、6件目が投稿されたら1件目を削除しなくてはならない。まずは、ログファイルに何件の投稿があるのかを調べる必要がある。
#配列初期化
lineArray = []
#ファイルを開いて中のデータを格納
fp = open(FILENAME)
fp.each{|line|
#配列の末尾にデータを追加
lineArray.push line;
}
fp.close
#ログの件数を取得
arrayCount = lineArray.nitems
さーて、一気に色々なものが出てきた。
配列の操作
lineArray = [] で配列を初期化している。PHPのように $lineArray = array(); などとする必要はない。
配列にデータを追加するには、lineArray[0] = 'hoge' のように配列の要素番号を指定して代入することもできるが、今回は1行ずつ自動的に配列化したいので、配列の末尾にデータを代入してくれる .push を使うことに。
lineArray = ['piyo'] lineArray.push 'hoge' print lineArray[1] #hogeが表示される
ループ
ログファイルを開いて、fp.each を使ってポインタを1行ずつ進めて、 line に内容を入力していくループ。
xxx.each{|hoge|
#処理
}
もうこの構文を最初見たとき???って感覚だったけど、やっていく内に慣れるんだろうか。
配列の要素数
配列にすべてのデータを入力したら、配列の要素数=ファイルの行数を取得する。.nitems で偽(=nil)以外の要素の数を返してくれる。
これで、現在のログが何件あるのかを取得することが出来た。次は件数が5件以上ある場合と、ない場合でログファイルに書き込む内容を変更する分岐を作る。
ログの件数によって書き込みの内容を変更する
LOGMAX = 5 arrayCount = lineArray.nitems #もしログの件数が指定の件数以上だったら if arrayCount >= LOGMAX then writedata = '' temp = LOGMAX - 1 #配列の先頭 lineArray[0] 以外を書き込み用変数に入力 for i in 1..temp do writedata += lineArray[i] end #入力データを追記する writedata += "\n" + cgi["textdata"] #書き込みモードを全書き換え mode = "w" else #指定の件数未満なので、ファイルの末尾に入力データを保存する #入力データを追記する writedata = "\n" + cgi["textdata"] #書き込みモードを追記へ mode = "a" end #ファイル書き込み fp = open(FILENAME,mode) fp.print writedata fp.close
ログの件数が LOGMAX 以上だった場合、lineArray 配列の先頭要素以外+投稿されたデータを普通の文字列データに変更するために、for で必要な要素を取り出す処理をしている。
ここで for のループを使用しているが、Ruby の for はちょっと特殊。
for i in 1..n do #繰り返し処理 end
1 から n まで繰り返し処理が行われ、i にその 1 から n の数字が入る。ただし、1 から n までみたいな単純なループだけではなく、
for i in [1,3,5] do print i end #1 3 5が出力される
と、範囲オブジェクトを渡すことで、このような面白い使い方も出来る。
最後に、書き込みモードを w か a に設定しログファイルに出力して終了。
ログファイルからの読み込み
ログファイルへの書き込みでやったのと同様、ファイルを開いて、.each で内容を取得していく。
#ファイルを開く
lineAll = ''
fp = open(FILENAME)
fp.each{|line|
lineAll += '読み込んだ際にHTMLタグをエスケープ
しかし、これだけではHTMLタグなどがログファイルに書き込まれていると、タグをそのまま表示してしまって非常に危険。なので、ファイルの内容を取得する際にHTMLエスケープ処理を入れる。
#ファイルを開く
lineAll = ''
fp = open(FILENAME)
fp.each{|line|
lineAll += 'CGI.escapeHTML() に渡すことによって、エスケープすべき文字をすべてエスケープしてくれる。ちなみに、ログへの書き込み時にエスケープするのではなく、表示時にエスケープ処理する方が色々と便利だと思う。というか現実的。
すべてを組み合わせる
長々と出てきた処理を組み合わせると以下のようなスクリプトになる。
#!/usr/local/bin/ruby
#ライブラリ読み込み
require "cgi"
cgi = CGI.new
#print "Content-type: text/html\n\n"
#これの代わりに
puts cgi.header(
"type" => "text/html",
"charset" => "EUC-JP"
)
#ログファイルの設定
FILENAME = "01.dat"
#最大書き込み可能件数
LOGMAX = 5
#最大文字数(2byte文字だと半分)
MAXLEN = 50
#入力データが空でなければ格納
if cgi["textdata"] != "" then
#文字数チェック
if cgi["textdata"].size <= MAXLEN then
#配列初期化
lineArray = []
#ファイルを開いて中のデータを格納
fp = open(FILENAME)
fp.each{|line|
#配列の末尾にデータを追加
lineArray.push line;
}
fp.close
#ログが指定行以上にあった場合、古いのは捨てる
arrayCount = lineArray.nitems
if arrayCount >= LOGMAX then
writedata = ''
temp = LOGMAX - 1
for i in 1..temp do
writedata += lineArray[i]
end
#入力データを追記する
writedata += "\n" + cgi["textdata"]
#書き込みモードを全書き換え
mode = "w"
else
#入力データを追記する
writedata = "\n" + cgi["textdata"]
#書き込みモードを追記へ
mode = "a"
end
#ファイル書き込み
fp = open(FILENAME,mode)
fp.print writedata
fp.close
else
errorMsg = "<p><strong>文字数が#{MAXLEN}を超えています</strong></p>"
end
end
#ファイルを開く
lineAll = ''
fp = open(FILENAME)
fp.each{|line|
lineAll += '<li>' + CGI.escapeHTML(line) + '</li>'
}
fp.close
#入力データが空でなければ格納
if lineAll != "" then
strdata = '<ul>' + lineAll + '</ul>'
else
strdata = '<p>ファイルは空っぽ</p>'
end
print <<EOM
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=euc-jp">
<title>Rubyで外部ファイルを読み込んで書き込む</title>
</head>
<body>
<h1>Rubyで外部ファイルを読み込んで書き込む</h1>
<h2>1行掲示板</h2>
<form action="01.cgi" method="post">
<input type="text" name="textdata" value="">
<input type="submit" value="入力文字を表示">
</form>
#{errorMsg}
<h3>仕様</h3>
<ul>
<li>HTMLタグは使えません</li>
<li>全角25文字まで入力受付</li>
<li>最大5件ログを保存</li>
<li>新着順に表示</li>
</ul>
<h3>ログ表示</h3>
#{strdata}
<p><a href="http://blog.tofu-kun.org/070903003706.php">ブログへ戻る</a></p>
</body>
</html>
EOM
なお、print "Content-type: text/html\n\n" としていたが今回は
puts cgi.header( "type" => "text/html", "charset" => "EUC-JP" )
にしている。cgi.header() の方が覚えやすいし、幅広く設定できるので吉。
- Newer: Rubyはじめました3:Ruby本を買いました。
- Older: Rubyはじめました:RubyでCGI.フォームデータを受けとる
Comments:0
Trackbacks:0
- TrackBack URL for this entry
- http://blog.tofu-kun.org/mt-tb.cgi/342
- Listed below are links to weblogs that reference
- Rubyはじめました2:ファイル読み込み&書き込みで掲示板 from Webプログラマー+WebデザイナーなZARU日記
