Ruby ビギナーのための CGI 入門 【第 4 回】 2 ページ

前のページへ 目次へ

目次

今号の CGI プログラム

今号以降は基本的に 12 号で作った掲示板を拡張していきます。 構成も同じで、bbs.rb, bbs.html, update.rb, bbs.dat となります。 これらのプログラムを改良したり、 問題を修正したりするのが今後の課題となります。

12 号の掲示板は動作はしますが、実用には耐えません。 今号では下記の点を修正をすることで実用レベルに近づけましょう。 その内容についてはそれぞれの項で詳しく説明します。

  • フォームデータの扱いと GET, POST
  • 新着順表示
  • デザイン
  • 分割表示

参考のために元の一行掲示板を rubima015-cgi\rubima12 に、 修正後の掲示板を rubima015-cgi\bbs というフォルダーに保存してあります。 どこが変わったのか知りたい時は それぞれのプログラムを比べて見て下さい。

フォームデータの扱いと GET, POST

form タグの method 属性

form タグには method 属性というものがあり、 その値によってフォームデータの処理方法が決定されます。

気づいた方もいるかもしれませんが、 14 号までのプログラムは method 属性に GET が指定されています。 GET の場合、これまでと同じく URL の「?」の後ろにフォームのデータが付加されます。

具体例を見てみましょう。 12 号ではフォームデータのやり取りの際、 下のような URL が使われていました。

http://localhost:8080/bar1.rb?t=dddd&s=Button

この方法だとフォームのデータが URL に付加されるので、 どんなデータが CGI プログラムに送られるのか URL を見れば分かります。

method 属性には GET だけでなく、POST という値も使用出来ます。 こちらは URL に付加せずにフォームデータをやり取りします。 その処理は CGI オブジェクトがやってくれるので、 今は知らなくても結構です。

一般に GET は何かの情報を取得する時に使い、 POST は新しいデータを書き込んだりする時に使います。 掲示板は投稿、つまり、書き込みなので、 POST を使う方が良いとされています。

ちなみに GET では URL の「?」後ろに フォームデータを付加するだけなので、 実はフォームを介する必要はありません。 例えば、ブラウザの URL の入力部部分に フォームデータ付きの URL を直接入力しても フォームを介した場合と同じように処理されます。

修正方法

修正は簡単です。 bbs.html の form タグの method 属性を post に変えるだけです。

<form action="./update.rb" method="post">

自分でフォームデータを処理している場合、 GET から POST への変更はプログラム自体の修正が必要になります。 しかし、CGI オブジェクトを利用している場合は このような場合でも自動的に対応してくれます。 CGI クラスの便利な点の一つです。

新着順表示

ここでは 12 号で指摘した、古い順に表示される という問題を修正しましょう。 同時にデザインのための修正も行います。

投稿データ bbs.dat について

まずは投稿データの bbs.dat の復習です。

12 号で作った一行掲示板では一回の投稿につき bbs.dat に一行ずつデータが追加されます。 つまり、bbs.dat の一行は一投稿に対応します。

前回の一行掲示板では一括してこのデータを読み込んで そのまま表示させていました。 そのために一番古いデータが先頭に表示されていました。

改造方針

新着順表示するために bbs.dat の最終行のデータが 先頭に表示されるように変更しましょう。

ここからは下のような bbs01.dat を使って説明します。

1 初めての投稿です。
2 テストです。
3 うまく表示されると良いですね。
4 うーん、どうかなあ。

このデータを新しい順に表示するとなると、

4 うーん、どうかなあ。
3 うまく表示されると良いですね。
2 テストです。
1 初めての投稿です。

となります。

新着順に表示させるには幾つか方法があります。 今号ではデータを一行ずづ読み込んで Array に格納し、 その Array を逆順に表示させる方法をとります。 上の bbs.dat のデータを Array に格納すると、 下のようになります。

["1 初めての投稿です。",
 "2 テストです。",
 "3 うまく表示されると良いですね。",
 "4 うーん、どうかなあ。"]

逆順に Array の中身を表示させる方法は 前ページと同じく while を使います。

File クラス の gets

一行ずつデータを読み込むには File オブジェクトの gets メソッドを利用します。 ここでは gets を利用したプログラムを書いて、 RDE から実行してみましょう。

gets.rb

f = File.open("bbs.dat", "r")
arr = []
l = f.gets

while l 
  arr << l
  l = f.gets
end

f.close

i = arr.length - 1

while i >= 0 
  print arr[i]
  i = i - 1
end


最初の 3 行は準備です。 gets.rb の 3 行目で f.gets によって変数 l に bbs.dat の 1 行目が読み込まれます。

この後、5 行目の while の条件チェックに入ります。 1 行もデータが無い場合は gets の結果は nil になりますので、 変数 l は nil となり while 文は無視されることになります。 逆に 1 行分のデータが変数 l にあれば、 while 文の繰り返しが実行されます。

while 文の繰り返しでは読み込みんだ 1 行分のデータを 変数 arr に格納しています。 その後に次の 1 行分のデータを f.gets で変数 l に代入します。 データをすべて読み込んでしまった場合、 f.gets は nil を返すので、変数 l に nil が代入されて while 文の条件が偽になり、繰り返しが中止されます。

bbs.rb への変更

では、bbs.rb を変更します。

元の bbs.rb と区別するために 変更後の bbs.rb を bbs01.rb とします。 では、最初に bbs01.rb を示します。

bbs01.rb

#!/usr/local/bin/ruby
require 'cgi'

f = open("bbs01.dat", "r") 
arr = []
l = f.gets

while l 
  arr << CGI.escapeHTML(l)
  l = f.gets
end

f.close



print "Content-type: text/html\n\n"

print <<EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
  <link rel="stylesheet" href="theme/blue-border/blue-border.css" type="text/css">
  <title>simple BBS </title>
</head>
<body>
<h1>簡易掲示板</h1>
<hr class="sep">

EOF



i = arr.length - 1
while i >= 0 
print <<EOF
  <div class="day">
    <div class="body">
      <div class="section">
      #{arr[i]}
      </div>
    </div>
  </div>
EOF
  i = i - 1
end



print <<EOF

<hr class="sep">

<div class="footer">
</div>

</body>
</html>
EOF


12 号の掲示板と比較すると、 かなり大きな変更が加わっています。 順に見ていきましょう。

4-13 行は掲示板データの読み込みです。 先ほどの gets.rb と同じようにデータを 読み込んで Array に格納しています。 気をつける点は Array の中身が HTML エスケープ済みになっている点です。

17-32 行は掲示板の HTML の一部を表示させています。 ここでは link タグに変更が加わっていたり、 h1 タグが使われたりしていますが、 基本的にデザインや表示に関するものです。

次の 36-48 行が今回のメインの部分です。 前ページの 繰り返し で説明したように Array の中身を逆順に参照しています。 繰り返される部分は 38-47 行の print と添字の減少部分です。 文字列の変数埋め込みで Array の中身が利用されています。 仮に、変数 arr が [“first”, “second”, “third”] なら、 36-48 行で下のように表示されます。

 <div class="day">
   <div class="body">
     <div class="section">
     third
     </div>
   </div>
 </div>

 <div class="day">
   <div class="body">
     <div class="section">
     second
     </div>
   </div>
 </div>

 <div class="day">
   <div class="body">
     <div class="section">
     first
     </div>
   </div>
 </div>

52-61 行も掲示板としての HTML を表示させているだけです。 hr タグや div タグが新たに付け加わっていますが、 これらも表示の問題です。

デザイン

デザインは既存のものを再利用しましょう。 全部自分で作るのは労力がかかりますからね。

tDiary のテーマ

先ほど逆順表示とデザイン用の変更を施しましたが、 本来はあれだけでは掲示板の見た目に大きな変化はありません。 上で行った変更は tDiaryのテーマ をデザインに利用することを前提にしています。

上記の URL にある tDiary のテーマのうち 少なくとも GPL でライセンスされたテーマは 自由に使うことが可能です。 (ただし、修正したテーマを他人に使われても文句は言わないで下さい。 詳細については GPL もしくは GPL日本語訳 を参照して下さい)。 他のライセンスのテーマに関しては ご自分でそのライセンスを読んで利用可能か確認しなければなりません。

今号の掲示板にはあらかじめ rubima015-cgi\bbs\theme というフォルダーにテーマを 2 つ保存してあり、 bbs.rb は bule-border というテーマを利用するようになっています。

テーマを利用するには bbs01.rb が表示する HTML のうち link タグの href 属性を変更します。

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
  <link rel="stylesheet" href="theme/blue-border/blue-border.css" type="text/css">
  <title>simple BBS </title>
</head>

例えば、同梱されている mini-g に変えたい場合は

<link rel="stylesheet" href="theme/mini-g/mini-g.css" type="text/css">

と変更します。

さらに別のテーマに変えたい場合は tDiary のテーマを rubima015-cgi\bbs\theme 以下のフォルダーに保存して、 その中の xxx.css (xxx はテーマ名によって異なります) を link タグの href 属性に指定します。

CSS

tDiaryのテーマは CSS を利用しています。 自分でテーマを書いたり、改造したりするには HTML や CSS の知識が必要になります。 その詳細は HTML と CSS に関する文章を参照して下さい。

CSS の利点

大雑把に言って CSS はデザインするためのものです。 しかし、HTML にもデザインのための色々なタグや属性があります。 そのため CSS の利点は HTML だけを書いている時には あまり感じないかもしれません。

CSS の利点の 1 つはデザイン部分を HTML から 分離することが出来ることです。 例えば、今回の掲示板プログラムの HTML に 文字の大きさや色を変えるタグや属性を入れてしまうと、 デザインを変えたいと思った時に CGI プログラムそのものを 変更しなければなりません。 プログラムの変更は大変ですが、 CSS を利用すればこうしたわずらわしさはなくなります。

上の bbs01.rb でも blue-border と mini-g では見た目が違いますが、 プログラムの変更は最小限度にとどまっています。

分割表示

新着順表示の変更はうまくいきましたが、 掲示板データが100件や1000件になると、 そのデータがすべて表示されてしまい実用的ではありません。 そこで、掲示板データを分割して表示するように改造しましょう。 今回は10, 20, 50, 100件ごとに表示されるようにします。

分割表示するのは良いのですが、 一度に表示する件数や表示する番号などを どのように CGI プログラムに伝えれば良いのでしょうか。 ヒントは先ほど説明した form タグの method 属性の GET にあります。

上で述べたように GET ではフォームを介さずに CGI プログラムに データを渡すことが出来ます。 HTML の a タグの href 属性でも「?」の後ろにデータを 付加することで、CGI プログラムにデータを渡す事が出来ます。 この性質を利用します。

掲示板にアクセスがあった時、 現在表示されているページの表示件数や 表示している掲示板データの最終番号 (表示されたデータの中で一番最後のデータのbbs.datにおける行数)をチェックしておきます。 掲示板データを表示する時に これらの値を使って 次のページ、前のページへのリンク、 表示件数を変更するリンク をいっしょに作り、 簡単にページ間を移動出来るようにします。

例えば、現在表示している掲示板データの最終番号が 30 で、 表示件数が 20 番とします。 この時、bbs.dat の 11-30 行目のデータが表示されています。 それとともに下記の 6 つのリンクが作られます。 それぞれのリンクにはデータ付きの URL が href 属性に指定されおり、 それぞれのリンクをクリックすると表示件数と最終番号が CGI プログラムに渡されてそれに対応するページが表示されます。

リンク名 表示件数 最終番号
前のページ 20 10
次のページ 20 50
10件ごと 10 30
20件ごと 20 30
50件ごと 50 30
100件ごと 100 30

href 属性に指定する値は フォームデータの形式に従うと下のようになります。 num や en は表示件数と最終番号に相当します。 使用する名前は num や en である必要はありません。 自分に分かりやすい名前を付けてもらって結構です。

  • bbs02.rb?num=20&en=10
  • bbs02.rb?num=20&en=50
  • bbs02.rb?num=10&en=30
  • bbs02.rb?num=20&en=30
  • bbs02.rb?num=50&en=30
  • bbs02.rb?num=100&en=30

この値を a タグの href 属性とすることで ユーザーがそのリンクをクリックすると、 num や en のデータが掲示板プログラムに渡されます。 上の例で「次のページ」がクリックされると、 num の 20、en の 50 という値が掲示板プログラムに渡されます。 この値を受け取った掲示板プログラムは 31 から 50 までの bbs.dat の掲示板データを表示させる事になります。

フォームデータは CGI クラスのオブジェクトを介して CGI プログラム側から参照出来るようになります。 表示件数と最終番号を受け取った CGI プログラムは それに対応する掲示板データを表示させ、 それに加えて上の6つのリンクも表示させます。

改造後の bbs02.rb を示します。 主に前半に大きな変更が加わっています。 順に変更点を見ていきましょう。

bbs02.rb

#!/usr/local/bin/ruby

require 'cgi'

filename = "bbs02.rb"
num_list = [10, 20, 50, 100]

f = open("bbs02.dat", "r") 
arr = []
l = f.gets

while l 
  arr << CGI.escapeHTML(l)
  l = f.gets
end

f.close

cgi = CGI.new

en = cgi["en"]
if en == ""
  en = arr.length - 1
else
  en = en.to_i
  if en < 0 or en > arr.length - 1
    en = arr.length - 1
  end
end

num = cgi["num"].to_i
if num <= 0 or num > 100
  num = 20
end

st = en - num + 1
if st < 0 
  st = 0
end

link = "<div class=\"navi\">"
if st > 0 
  link = link + " <a href=\"#{filename}?num=#{num}&en=#{st-1}\">前のページ</a> "
else
  link = link + " 前のページ "
end  

if en < arr.length-1
  link = link + " <a href=\"#{filename}?num=#{num}&en=#{en+num}\">次のページ</a> "
else  
  link = link + " 次のページ "
end
num_list.each do |i|
  link = link + " <a href=\"#{filename}?num=#{i}&en=#{en}\">#{i}件ごと</a> "
end
link = link + "</div>"


print "Content-type: text/html\n\n"

print <<EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
  <link rel="stylesheet" href="theme/blue-border/blue-border.css" type="text/css">
  <title>simple BBS </title>
</head>
<body>
<h1>簡易掲示板</h1>
#{link}

<hr class="sep">
EOF



i = en
while i >= st 
print <<EOF
  <div class="day">
    <div class="body">
      <div class="section">
      #{arr[i]}
      </div>
    </div>
  </div>
EOF
  i = i - 1
end



print <<EOF

<hr class="sep">
#{link}
<div class="footer">
</div>

</body>
</html>
EOF


前半部分の変更点

まず 5-6 行目です。 filename というのは bbs02.rb のファイル名です。 リンクを作る際に bbs02.rb の名前が必要になるので、 ここで変数に代入しておきます。 この値は頻回に使うので、変数に代入しておき、 プログラムの変更が必要になった時に 何度も同じような修正をしないで済むようにしておきます。 次の num_list には表示件数の値が格納されてます。 今回は 10, 20, 50, 100 件ずつ表示させるので、 その 4 つの値が格納されてます。 この値もリンクを作る時に利用します。

8 行目は掲示板データのファイル名が変わっていますが、 説明のために変更しているだけです。

中盤の変更点

19-59 行が今回のメインの変更点になります。 ここの前半部分では GET で取得したデータのチェックが行われます。 後半では前後のページへの移動や件数表示変更のためのリンクを生成します。

その 1

前半部分は 19-39 行になります。

19 行では今までと同じように CGI クラスのオブジェクトが作られます。

21 行で CGI のオブジェクトから en に対応するフォームのデータを取得しています。 このデータが今回表示する掲示板のデータの最終番号に 相当するということは既に述べました。

22−29 行は取得したデータのチェック部分です。 2回 if 文が使われているので、少しとまどうかもしれませんが、 ゆっくりと考えてみましょう。

22 行目のif 文では en に対応するフォームデータあるかどうかを チェックしています。例えば、最初に掲示板にアクセスした場合、 最終番号や表示件数は指定されていません。 そのためにこうしたチェックが必要になります。 通常、何も指定が無い場合は 最新の投稿、すなわち、一番最後の投稿を表示させますので、 一番最後の投稿にあたる Array の数より 1 小さい値を指定します。

25-28 行は en に対応するフォームデータが存在した場合です。 25 行目では to_i というメソッドが使われています。 これはオブジェクトに数字になれという命令です。 フォームデータは文字列のデータなので、 25 行目までは “1” や “56” のようなデータが変数 en に代入されています。 しかし、変数 en の値には数字としての役割が期待されるので、 ここで to_i メソッドで数字に変更します。 こうした処理が必要なのは Ruby では数字の 1 と 文字列の “1” が別物だからです。 この点をしっかりと覚えておいて下さい。 ちなみに to_i では数字に変換できない場合は 0 になります。

26-28 行では最終番号としての変数 en の値が妥当かどうかをチェックしています。 変数 en の値が投稿データの最終番号より大きかったり、 0 より小さいということはありえません。 そうした場合、指定が無い場合と同じように最新の投稿が表示されます。 26 行目の en < 0 or en > arr.length - 1 はそのチェックです。 この行は 変数 en が 0 より小さいか、 Array の中身の個数より 1 小さい数より大きいか否か という意味です。 or はその前後のいずれかが真になれば全体で真となります。 つまり、en < 0 もしくは en > arr.length - 1 のいずれか 一方が成立すれば 27 行目が実行されます。

31-34 行は表示件数のチェックです。 32 行では表示件数の値をチェックしています。 今回は 10, 20, 50, 100の4つを使うので、 その範囲に変数 num の値が含まれるかどうかをチェックします。 範囲にない場合、 20 が変数 num に代入されます。 前回と違って 31行 ではいきなり to_i を実行しています。 フォームデータが無い場合、変数 num には “” が代入され、 num.to_i で 0 になります。 0 以下の場合も表示件数としては不適ですので、 変数 num に 20 を代入します。 一方、22-29行では 0 が最終番号として妥当な値なので、 指定が無い場合と 0 が指定された場合とをしっかりと 区別しないければなりません。 その違いがプログラムに出ています。 31-34行を22-29行と同じように書くと下のようになりますが、 長くて少し面倒ですね。

num = cgi["num"]
if num == ""
  num = 20
else
  num = num.to_i
  if num <= 0 or num > 100
    num = 20
  end
end

次の 36-39 行は変数 st の代入とチェックを行っています。 変数 st は表示される掲示板データの一番古いデータの 番号にあたります。変数 en の数で終わって、 変数 num の数だけ投稿を表示させるには en - num + 1 の番号から表示させることになります。

その 2

ここからは残った 41-59行の説明です。 ここでは前後のページへのリンクや 件数表示を変更するためのリンクを作成します。

説明を始める前に これまで使った変数とその役割について見ておきましょう。 下表のようになります。 このうち変数 cgi はリンクの生成部分では使用しません。

変数名 役割
en 表示する掲示板データの最後の番号
st 表示する掲示板データの最初の番号
num 一度に表示する掲示板データの個数
cgi CGI クラスのオブジェクト
filename bbs02.rb の名前
num_list 表示件数の一覧

では、説明に戻りましょう。 41 行以降では変数 link にリンク部分の HTML が付け加わっていきます。 41 行では最初に div タグをつけています。 これは HTML のタグをまとめるために使用します。 「\“」 というのは 11 号で紹介しましたが、覚えていますか? これは文字列の区切りとしての「”」ではなく、 「”」そのものを意味しています。

次の 42-46 行は前のページへのリンクを生成しています。 42 行では一番古いページかどうかをチェックしています。 変数 st は表示される掲示板データの始まりの番号です。 これが 0 より大きければ、まだ表示するデータが残っているはずです。 その場合は 43 行で前のページへのリンクを作ります。 そうでない場合は 45 行で前のページという表示だけにします。 両方で link = link + “…” という部分がありますが、 これは変数 link に “…” の部分を追加するという意味です。 例えば、”1” + “2” は “12” になります。 43 行では前ページへのリンクを作っています。 前ページの最終番号は今表示している始めの番号より 1 小さくなるはずです。 そのため en=#{st-1} となります。 他の部分は変数の意味がわかれば、難しくないと思います。

48-52 行は次のページへのリンクを作っています。 次ページの最終番号は現在表示している最終番号より 表示件数分大きくなります。 現在表示している掲示板データの最後が en なので、 次のページの最初の番号が en + 1 になります。 そこから num の分だけ表示させると、 次ページの最終番号は en + num になります。

53-55 行は表示件数の変更リンクを作っています。 num_list に表示件数の数字が格納されていて、 each メソッドを使って順に追加しています。

56 行は div タグを閉じているだけです。

後半部分の変更点

59行より後ろの部分には大きな変更点はほとんどありません。 順に見ていきましょう。

まず 61-75 行の HTML 表示部分で変数 link の埋め込みがあります。 上で作ったリンクをここで利用しています。

次の変更点は 79-80 行です。 変数 en は最終番号で、 ここから始まって保存順とは逆順に表示させていきます。 表示部分や繰り返しに関しては bbs01.rb と大きな変化はありません。 一番最初に表示される番号や while 文の条件部分のみが異なります。

最後の変更点は 98 行です。変数 link をここでも 表示しています。これは無くても良いのですが、 あったほうが利用者には便利です。

おわりに

今回は主に bbs.rb 側の変更を行いました。 新着順表示、分割表示が実装されて 表示部はかなり改善されました。

次回以降は更新側の update.rb について 変更を加えていきます。

筆者についてというか残業時間について

えーと、最近、忙しくてなかなか帰れません。で、当然残業になります。 それは良いのですが、 何故かうちの職場は 1 日あたりの残業時間が2時間単位で分類されています。 自分で記録する時は 1 分単位なのに…。 そんな適当で良いのかよ>うちの職場。

前のページへ 目次へ