CGI で使うことのできるデータベース

データをプレーンテキストの形式で書き込む open -- end とは異なるファイル入力出力方法を学ぼう。 データベースとして入力する。

PStore 専用 directory の作成

訪問者がデータを入力してくれたデータを保存したいというときは、 計算機に「第三者 others も書き込む」という宣言をする。

directory を全て書き込み可能にすると、訪問者が侵入者であったり、 あるいは自分のプログラムのミスなどで、 全て破壊してしまうかもしれない。そこで、 データベース専用の directory を作り、 その中だけ第三者を書き込み可能 にする。CGI が動く cgiruby の下に pstore という directory を作成し、それだけを第三者書き込み可能とする。


% cd ~/public_html/rubycgi
% mkdir pstore
% chmod o+w pstore

この中に、入力されたデータを出力し、 さらにデータベースにデータをしまうプログラムを作る。 データベースは、pstore の中に作られ、その名前は自分で自由に決めることができる。

データの名前 データ.db を決めたら、


% touch データ名.db 
% chmod o+w データ名.db 

と、あらかじめデータファイルを作る。

データベースにしまったデータを書き出すプログラムは、 プログラムから得られた値を機械が 書き込むことができるよう


% chmod o+w プログラム.rb

する。 また、Ruby プログラムなので chmod +x も忘れないこと。


% chmod +x プログラム.rb

プログラム部分について

Hash の仕組みを利用し、 データベースに訪問者が入力したデータを組み込むことができる。 CGI ライブラリの他に、PStore ライブラリを使用し、 CGI 変数をデータベースにしまうことにする。 プログラム内での構造は

受け取ったデータをためる宣言 require 'pstore'
データベースの作成 PStore 変数 = PStore.new("データベース.db")
(データを入力させる部分) <form></form> tag で作成する部分
入力されたデータを読み込んで出力する部分
PStore 変数.transaction do
-- CGI 変数に代入された値を表示させる -- end

となる。

ファイルの構成

初めてプログラムを起動したときには、


% ls pstore
起動するプログラム.rb

となるが、書き込まれたあとは、


% ls pstore
起動するプログラム.rb    データベース.db

となる。

PStore 変数 = PStore.new("データベースファイル名.db")

データベース データベースファイル名.db に書き込ませる。 表示させるときには、 毎度このデータベースを呼び出し、起動プログラムに書き込ませる。 起動プログラムに書きこませるには PStore 変数を使用する。

時間を Key として

入力されたデータをデータベースに入力し、ゲストブック、 ブログ、掲示板などを作ることができる。

時間を測ろう

t = Time.now.to_i などとして、変数 t を PStore 変数 の Key に指定すると、 ブログや掲示板を作ることができる。 Hash 型にしまわれるので、sort が必要である。


t = Time.now.to_i

掲示板

コメントを受け付けつつ表示する CGI スクリプトを作ろう。 cgi_bbs.rb とする。

黄色の 3 行を付け加え、ライブラリの準備を行う。 データをためるライブラリを加える。 データをためるデータファイルを決める。 動かしたいファイルを変数に代入する。 今はこのプログラム自体を動かすので、 プログラムそのものの名前を入れる。


require "cgi"
require "pstore"
cgi = CGI.new(:accept_charset => "EUC-JP")
db = PStore.new("cgi_bbs.db")
myprog ="cgi_bbs.rb"

毎回プログラムを動かして HTML 文書を書き出す。 CGI スクリプトの部分は今までと同じである。



# 入力した時間を t に取りこむ

t = Time.now.to_i

print("Content-type: text/html; charset=EUC-JP\n\n")
print("<html>\n")
print("<head><title>庄内なんでも質問コーナー</title></head>\n")
print("<body>\n")

# データベースを扱う部分

# 投稿する部分

print("</body>\n")
print("</html>")

コメント受け付けプログラム

form 部品のところを作成する。


# 入力させる部分

printf("<form method=\"POST\" action=\"./%s\" \n", myprog)
print("<p>ハンドルネーム: <input name=\"nickname\" maxlength=\"40\"></p>\n")
print("<p>質問: </p>")
print("<p>
<textarea name=\"question\" cols=\"40\" rows=\"10\"></textarea>
</p>\n")
print("<p>\n")
print("<input type=\"submit\" value=\"送信\"\n")
print("<input type=\"reset\" value=\"リセット\"\n")
print("<\p>\n")
print("</form>\n")

ハンドルネームを cgi["nickname"] にしまい、 質問を cgi["question"] にしまう。 myprog は先の部分で、プログラムそのものを動かすように設定してある。

"" double quotation の部分は、CGI 変数と区別するために、 \"\" として表現する。

データベースに入力

掲示板では、一番最後に form tag があるので、 もらってきたデータを登録する部分は form tag の上に来るはずである。 form tag の上部に、登録する部分をいれよう。



db.transaction do 

  # データがなければ新規に開く部分

  if db == nil          # 新規のとき
     db[t] = Hash.new    # 時間を Key とした Hash 作成
  end


  # CGI 変数を受け取って、変数に代入する部分

  handlename = cgi["nickname"]
  request = cgi["questions"]

  db[t] = [handlename, request]
                      # 時間と記入した人、コメントを Hash のセットにする

end


# 入力させる部分

transaction method は、PStore 変数内で実行するときに用いる method である。

PStore 変数.transaction do
  
   ####   データベースにしまう処理   ####

end

データベースからとりだして表示

PStore 変数 db に transaction method を組み合わせ、 データを一気にしまう(取り出す) ために、 transaction method の中で、とりだそう。 先に登録し、そのあと全て書き出すという方法を取る。


db.transaction do 

    : 

  db[t] = [handlename, request]


  # データを表示する部分
  
  <span class="kinput">print("<ol>\n")
  
  for time in db.roots.sort # コメントの時間順で並べ替えた配列を調べる
    if db[time][0] != nil
      printf("<li>%s さん: %s</li>\n", db[time][0], db[time][1].chomp)
    end
  end
  
  print("</ol>\n")</span>

end

ol tag、ul tag や pre tag を使うと 1 行に 1 人分のコメントを整列させることができる。

データベースの内容をカウント

データベースにしまったものを取り出すには、 データがどのように入っているか調べ、それを眺めてから 取り出す方法を考える。

transaction method の中で、 データがどのように保存されているのか調べてみよう。

# p PStore 配列.roots   # しまっている全てのものを並べる
# p PStore 配列["key の名前"]  # PStore 配列にしまわれている "key" を調べる

もし何がしまわれているのか気になる場合は、試して調べてみよう。

ソースはここから、見ることができる。

CSS ソースはbbs.css からダウンロードすることができる。

見かけが完成しているかどうか、該当するページをブラウザで見て調べよう。 http://roy/~ユーザ ID/rubycgi/pstore/プログラム名.rb である。

例えば db.roots.length とすると、 何を得るか。それから分かることをどのように適用できるか、 考えてみよう。

ライブラリ cgi に関係した小技

puts を使うと改行文も含んだ print 文となる。 指示指定子の代わりに、

puts("#{変数}")

としてもよい。

h1 などの tag は、CGI 変数をしまう配列名.tag 名という method が使える。

CGI をしまう配列名 = CGI.new(:accept_charset => "EUC-JP",:tag_maker => "html4")

と変更すると使えるようになる。

puts(CGI をしまうHash 名.tag 名{表示させたい文字列})

とすると、tag でくくって表示する。

CSS による色の効果 (1 年次の復習)

各 tag における色は、 css ファイルを定義することによって作ることができた(1 年の復習)。 css ファイルは名前を link tag で指定し、同じ directory におく。今の場合

% ls 
プログラム.rb   スタイルファイル.css

スタイルファイルの一例はこのようになる。

*.rb ファイルを実行して HTML 文書を吐きだす場合には、 プログラムで link の項を調べ、 \"\" に書き変える必要がある。 ./プログラム.rb を Kterm で実行しながら、 エラーが出ないように調べ、直していくとよい。

ボタンの色を変える

css ファイルに input tag の色を変える指示を出せばよい。 画像を埋め込むこともできるが、現時点では OK ボタンのみ可能。

色については 色見本 などを参考に、 見やすい組み合わせを選ぶこと。なお w3.org は HTML 文書の総本山である。

データをカウントしよう

酒田駅から出発した乗客は何人がどこへ向かったか、乗降客数を割りだしたい。

データを集めるページでデータに書き込む (券売機プログラム) --> データを見せるページでデータを処理する (売上げ計算プログラム)

という方法が考えられる。例えば ticket_db.rb を作る。

ticket.htmlrubycgi/pstore/ticket_db.html としてコピーする。 form tag では、ticket_db.rb を動かすように変更する。 見かけは こちら

データベース名は ticket.db とする。

% touch ticket.db 
% chmod o+w ticket.db 

とする。 後期第 8 回で作成した cgi_ticket.html , cgi_ticket.rb rubycgi/pstore にコピーし、それぞれ db_ticket.html, db_ticket.rb と変更しておく。

db_ticket.html では db_ticket.rb を動かすように変更する。

乗降データの記録

db_ticket.rb では、CGI と PStore の宣言を行う。


cgi = CGI.new(:accept_charset => "EUC-JP")
ticket = PStore.new("ticket.db")

購入した行先駅の切符のデータを行先駅の変数 arv に代入する。


# arv = "鶴岡"
arv = cgi["arrive"]

購入した時間を ID とするようなデータを ticket.db に保存するようにする。


time = Time.now

ticket.transaction do
  if ticket == nil
    ticket[time] = Hash.new
  end

  ticket[time] = [dpt, arv]
                 # 出発駅と到着駅を記録
end

日時表示もつけてみる。

見かけは このように なる。

営業終了後、駅員が売上げを調べるプログラムを 動かす

日付の自動記録

Time method を使うと、日時等を判断してくれる。

# Time.now       # 日付と時刻表示
# Time.now.year  # 年
# Time.now.month # 月
# Time.now.day   # 日
# Time.now.hour  # 時
# Time.now.min   # 分
# Time.now.sec   # 秒

また、日付や時刻を指定して表示させることもできる。

乗降客数を調べよう

乗降客を調べるdb_sales.rb を作ろう。

乗降客数を調べるために、データベースを拾って来る部分が必要である。

ticket = PStore.new("ticket.db")

またデータを読みこませる部分は以下の通り。


sales = Hash.new

ticket.transaction do
  for time in  ticket.roots
    sales[time] = ticket[time]
                   # Hash sales にデータを読みこませる
  end
end

p sales で中身を調べてみると

% ./db_sales.rb        [~/public_html/rubycgi/pstore]
Content-type: text/html; charset=EUC-JP

{2012-09-21 18:28:05 +0900=>["酒田", "東酒田"], 2012-09-21 18:28:11 +0900=>["酒田", "余目"], 2012-09-21 18:28:16 +0900=>["酒田", "西袋"], 2012-09-21 18:28:22 +0900=>["酒田", "余目"]}

購入日時と購入駅名、行先がしまわれていることが確認できる。

データを解析するには、sales という Hash を改めて調べ直して、 到着する駅名が出現した場合に value に 1 を加えることにしよう。


peak = Hash.new(0)   # Hash 値が数値であることを明示する

for time in sales.keys
  stop = sales[time][1]
  peak[stop] += 1
end

p peak の結果を調べる。

{"東酒田"=>1, "余目"=>2, "西袋"=>1}

peak の出力を乗降客の多い順に並べ替えてきれいに出力するようにすれば、 完成である。

駅の配置順、特急の泊まる駅だけを取りだすなど、 さらなる検索ができるようにするには、どう工夫したらよいだろうか。

見かけは こちら

前期を参考に、乗車券の料金と人数を組み合わせた 売上げ一覧を作ってみよう。

HTML 形式で Web サイトに結果を出力するならば、cgi_bbs.rb 等を参考につくることができる。

データベースからの削除

消したい場合には、key を指定して、データベースから取り除くことができる。

データベース 変数.delete(key)

例えば、管理者権限で削除するような bbs_admin.rb を作る。 発言の番号は時間の配列要素番号から -1 したものに等しいことを利用する。

入力する部分を取り除き、表示部分だけを残したページを作る。


  :
myprog = "bbs_admin.rb"
  :
 db.transaction do 
  print("<ol>\n")
  for time in db.roots.sort
    if db[time][0] != nil
      printf("<li>%s さん: %s</li>\n", db[time][0], db[time][1].chomp)
    end
  end  
  print("</ol>\n")
  :

次に、削除させる入力部分を作る。


printf("<form method=\"POST\" action=\"./%s\" \n", myprog)
print("<p>削除番号: <input name=\"delno\" maxlength=\"10\"></p>\n")
print("<input type=\"submit\" value=\"送信\">\n")
print("<input type=\"reset\" value=\"リセット\">\n")
print("<\p>\n")
print("</form>\n")

cgi["delno"] に入った番号を整数値とし、時間の配列の該当要素を探す。


no = cgi["delno"].to_i
db.transaction do
   t = db.roots.sort[no-1]  # 入力された時間の配列は第ゼロ要素から開始
   db.delete(t)
end

空欄の場合

入力が空欄の場合は、transaction で中止する部分を作ることができる。 また、すでにある空欄を管理者ページで使った技術を応用して作ることができる。

空欄を記録させたくないとき

入力させるプログラムでデータベース変数.abort が使える。


db.transaction do
  for time in db.roots.sort  #  コメントの時間順で並べ替えた配列を順に見る
    if db[time][1] != nil
      printf("
  • %s さん: %s
  • \n", db[time][0], db[time][1].chomp) else db.abort end end end

    空欄を削除する部分をつくるとき

    入力させるプログラムで空白がある場合、db[key] にしまわれている形を考える。 この場合、2 要素の配列であるから、 例えば以下のようにしてデータから除去することができる。

    
    db.transaction do
    
      for time in db.roots
        if db[time] == ["",""]
          db.delete(time)
        end
      end
    
    end
    
    

    open -- end の読み込み

    現在ブラウザは UTF-8 であり、 EUC-jp のプログラムやファイルを動かしているためとくに読ませる場合に問題が起きる。

    ラジオボタンで データを読みこませる とドロップダウンメニューの を出す。 データラジオボタンバージョンのソース ドロップダウンメニューのソース

    ver 1.9 から文字コードを明示する必要がでてきた。 うまくいかない症状として Kterm で実行すると動くのにも関わらず、 ブラウザで白い画面になる場合であり、 上記の文字コードの問題が起きているのが原因である。解決法としては

    1. FileOpen で r:euc-jp と、日本語コードを明示する。
    2. require 'kconv' をつける。
    3. 取りこんだデータを toeuc をつけて配列にしまう。

    1 は必ず必要。2, 3 は必要な場合がある。

    この値を引き継ぎたい

    foo = "A" という値をブラウザ上に見せずに

    <input type="hidden" name="var" value="#{foo}">

    var は Hash でいう key となり、foo は Hash でいう value としてkey, value のセットで以降のページへ引き継がれる。 foo はブラウザで表示されないので隠しデータという。 隠し変数 (hidden variables) と呼ばれる。 ブラウザのソースからは読めるので、 HTML を機械処理する場合(攻撃を仕掛けることも含め)を念頭に、 保守情報(電子メールアドレス)等は使用してはいけない。