復刻「Ruby活用編 〜 Ruby と PostgreSQL でメンバ制掲示板を作る 」

fsys.netドメインが無くなった(更新せず)ので思いだすのに張ってしまいました。
実はRailscgi 混在ってありかも。

時間ないのでテキストそのまま貼り付けたら悲惨になってるけど直す時間がないので
すみません。またあとで直します。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■■   Ruby活用編 〜 RubyPostgreSQL でメンバ制掲示板を作る 

 オブジェクト指向スクリプト言語 Ruby の活用事例として
 
   Linux + Apache + Ruby/eRuby + PostgreSQL 
   
 での メンバ制掲示板を紹介します。
 携帯でのアクセスを考慮した EUC Shift-JISコード変換や、
 Session管理、DBアクセスのクラス化実装など Ruby のエレ
 ガントさを少しでも、実感して頂けるようにお話したいと
 思います。RubyPostgreSQLでのシステム構築の参考に
 なれば、幸いです。

                       2002/05/26 LILO Monthly Seminor #19

                       福井 修 @Fsys(福井システムリサーチ)
                       o-fukui@po.iijnet.or.jp http://fsys.net

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■   自己紹介
────────────────────────────────────

    神戸 学園都市 で 1990/5より独立して「システム構築屋」さんとして活動

    神戸商工会議所 会員
    関西SOHO デジタルコンテンツ共同組合(KANデジ) 組合員
    http://www.kansai-soho.or.jp/

    企業、大学のシステム構築をサポート
    工場制御システム から 最近は Web-DB システムへ

    主なプロジェクト&プロダクト
         :
    姫路      形鋼圧延工場システム under K重工  VMS(C)
    台湾 高雄 形鋼圧延工場システム under K重工  OpenVMS[Oracle](C) + NT(VB)
    中国 上海 空港荷物搬送システム under K重工  True64[Oracle](C+Java+Perl)
                                                               + NT(C+Java)
    大阪 化学 グループスケジュールシステム      NT[Access](VB/ASP)
    神戸 部品 販売管理システム                  98[Access](VB/ASP)
    神戸 大学 関係先台帳システム                Linux[PostgreSQL](Java/JSP)
    大阪 語学学校 ファイル配信システム          Linux[PostgreSQL](Ruby/eRuby)
    神戸 地質調査 電話帳システム                Linux[PostgreSQL](Ruby/eRuby)
    神戸 重工 プラント情報システム              Windows2000[SQLServer](Ruby/erb/Wakaba)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■   Ruby 概要 (おさらい鳥瞰)
────────────────────────────────────
Rubyはシンプルかつ強力なオブジェクト指向スクリプト言語Ruby本家
http://www.ruby-lang.org/ja/

────────────────────────────────────
■ Rubyの特長など

・ 新鋭 なので いろいろな知見をもとに、よりよく改善されている。

   私の実感↓
   
   『 PerlJava の良いとこどり → Ruby 』

・ わかりやすくて、使いやすくて、機能が強力。
   grep が 数行で書けてしまう。

   使い出すと とりこ になって他の言語を使うのがいやになる。

   日本発(まつもとゆきひろ氏)なので、解説は、日本語で
   わかりやすい。日本語が、もともとサポートされている。

・ 根本からオブジェクト指向なので、継ぎ足してオブジェクト指向
   しているものよりエレガント
   
   C++, Perl, PHP...

   オブジェクト指向を、学習・実践するのに最適

   Javaオブジェクト指向にとまどったら Rubyで学べば理解できる^^

・ 国内はもとより海外での評価が高い
   eXtream Programing の 権威 Kent Beck さんが絶賛
   平成12年度「未踏ソフトウェア創造事業」採択
   
・ スクリプト言語なので、コンパイル不要でお手軽。
   ( Javaは、中間状態.class へコンパイルするが、実行はJavaVM上
     のインタプリタ )

・ オープンソースで、自由に使える。
   コミュニティで、開発のパワーが結集されている。
   
・ マルチプラットフォームなので、WindowsでもLinuxでもMac Xでも共通で使える。

・ Ruby は、Linux上で開発されています。

────────────────────────────────────
以下 教科書より
シンプルな文法 
普通のオブジェクト指向機能(クラス,メソッドコールなど) 
特殊なオブジェクト指向機能(Mixin, 特異メソッドなど) 
演算子オーバーロード 
例外処理機能 
イテレータクロージャ 
ガーベージコレクタ 
ダイナミックローディング (アーキテクチャによる) 
移植性が高い.多くのUNIX上で動くだけでなく,DOSWindowsMacBeOSなど
の上でも動く 

────────────────────────────────────
■■  Rubyの情報

・ 他の言語との比較
http://www.ruby-lang.org/ja/compar.htmlPerl -> Ruby の Tips
http://csl.toba-cmt.ac.jp/~kiri/ruby/tips/tips_p2r.html日本IBMのサイトで日本発最新のオープン・ソースの宝石 Maya Stodte
http://www-6.ibm.com/jp/developerworks/linux/ruby.html

・ 平成12年度「未踏ソフトウェア創造事業」採択プロジェクト
オブジェクト指向スクリプト言語Ruby次期バージョンの開発
http://www.ipa.go.jp/NBP/12nendo/12mito/index.html

・ JAOO国際オブジェクト指向カンファレンス(www.jaoo.dk)atデンマーク
http://www.ruby-lang.org/en/jaoo2001/

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■   Rubyプログラミングの基礎
────────────────────────────────────
■ 環境整備
────────────────────────────────────
・ ダウンロード
http://www.ruby-lang.org/ja/download.htmlRuby インストール
http://www.ruby-lang.org/ja/install.html
  
────────────────────────────────────
■ はじめの一歩 スクリプト言語としての Ruby
────────────────────────────────────

・ hello.rb ソースファイル作成

#!/usr/local/bin/ruby
puts "Hello, Ruby!"

・ 実行
$ ./hello.rb
bash: ./hello.rb: bad interpreter: Permission denied

実行権限がないと怒られます。

実行権限を付与してやります。
$ sudo chmod 755 hello.rb
Password:

では再度実行します。
・ 実行
$ ./hello.rb
Hello, Ruby


────────────────────────────────────
■ つぎに環境変数を出力してみます
────────────────────────────────────

・ env.rb ソースを作成

#!/usr/local/bin/ruby
#
#環境変数を標準出力に出力する
#
ENV.each do |name,value|
 
 puts name + ' : ' + value

end

・ 実行

 私の環境での実行例です。

$ ./env.rb
PWD : /home/fukui
CATALINA_HOME : /usr/local/tomcat
CLASSPATH : .:/usr/local/pgsql/share/java/postgresql.jar:/usr/local/jdk/lib/tools.jar:/usr/local/jdk
/lib/dt.jar:/usr/local/ant/lib/ant.jar
ANT_HOME : /usr/local/ant
USER : fukui
:

────────────────────────────────────
■ 環境変数を出力の要素 解説
────────────────────────────────────

この3行には、Ruby らしさ が詰まっています。要素を順番にみてみましょう。

	ENV.each do |name,value|
	 
	 puts name + ' : ' + value

	end

1) 組み込み定数

ENV : Rubyには組み込み定数と呼ばれる定数があります。ENVはそのうちの
      ひとつで、環境変数をハッシュで保持します。

2) ハッシュ(または連想配列)

 { hoge => valuehoge, fuga => valuefuga ... }

 ハッシュ式は、{ } で囲まれて、「キー」と「値」を => でつなげたものを
 カンマで区切られた並びで指定します。
 
 ex.1)
 lang_estimation = { "Ruby"=>"elegant", "Java"=>"complex", "VB"=>"poor" }

 p lang_estimation["Ruby"] #=> "elegant"

 ハッシュは、キーから値を巧妙なアルゴリズムで高速に検索できます

3) 配列  (インデックスのついたオブジェクトの集まり)

 [ "hoge", "fuga".. ]

 配列式は、[ ] で囲まれて、カンマで区切られた式の並びです。

 ex.2)

 lang_array = ["Ruby","Java","VB"]

 p lang_array[0] #=> "Ruby"


4) イテレータ (繰り返しの為のメソッド)

 .each は、 eachメソッドです。これはその名のとおり繰り返し
 を行うためのメソッドです。
 eachメソッドは、イテレータの代表で、配列やハッシュと供に
 使われます。


  オブジェクト.each do | 変数 |
    繰り返したい処理
  end


  この環境変数の例では、オブジェクトは ENV という組み込み変数名で
  指定(定義)された実体です。
 
  ENV.each do |name,value|
 
    puts name + ' : ' + value

  end

  繰り返しますが ENV は組み込み変数で 変数名と値のペアを
  ハッシュで保持したオブジェクトです。

  ここで イテレータeachメソッドを使うと、ハッシュの各要素に
  対して繰り返し、操作することができます。

  ここでは、ENV オブジェクトの変数名と値のペアを順番に
  取り出して、変数 name と value にセットしてくれます。

  そして繰り返しの処理

    puts name + ' : ' + value
  
   で 文字列 name に ':' をつなげて さらに 文字列 value も
   つなげて puts しているわけです。

 説明すると長くなりましたが Rubyスクリプトでは、簡潔に
 記述できることの一端がご理解頂ければ、幸いです。


────────────────────────────────────
■ オブジェクト
────────────────────────────────────
 すこしオブジェクト指向の基本を確認します。


     Ruby では、値 とは オブジェクトのことです

    fsys = "Ruby"

  これは  fsys というローカル変数は、"Ruby" という文字列(という値=オブ
  ジェクト)を指す ということを定義しています。

  "Ruby"という文字列も ひとつのオブジェクト(=値)です。

    lang_array = ["Ruby","Java","VB"]

  これは lang_array というローカル変数は、"Ruby","Java","VB"という要素
  をまとめるオブジェクトである配列を指すということを定義しています。


     Ruby では、変数は 参照 です。


     Ruby でない考え方では、変数は 容器 です。
     
     
     ここは、大事なポイントです。

  繰り返します。

    fsys = "Ruby"
  
  は、fsys という容器に代入しているのではなく "Ruby"という文字列
  オブジェクトが ローカル変数 fsys から参照できるようにする
  ということを定義しています。

  繰り返します。

     Ruby では、値 とは オブジェクトのことです

     Ruby では、変数は 参照 です。

────────────────────────────────────
■ メソッド
────────────────────────────────────

  メソッドを呼び出すには、次の形式。


      オブジェクト.メソッド

      obj.x()
 

  ex.1)

    fsys = "Ruby"
    fsys.length()

    これは、 fsys という変数名で参照されるオブジェクトに対して length() 
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    というメソッドを呼び出しています。
    ~~~~~~~~~~~~~~~~~~~~~~~~

    length()というメソッドでは、長さを得ることできますので

    fsys.length()
    
    では、長さ 4 が得られます。

    puts fsys.length #=> 4

    (メソッド引数のための()は省略できます)
     

────────────────────────────────────
■ 次に オブジェクト指向の要所である class を使った Ruby
────────────────────────────────────

・ oo.rb ソースファイル作成

#!/usr/local/bin/ruby
class Oo
  def initialize(x)
    @string = x
  end
  def println
    print @string, "\n"
  end
end

hello = Oo.new("Hello ObjectOriented Ruby!")
hello.println

・ 実行
$ ./oo.rb
Hello ObjectOriented Ruby!

#何で 1行文字を出力するのに、こんなに面倒なことをするのか は
#次に説明を進めます。

────────────────────────────────────
■ オブジェクト指向 class を使った Hello Ruby 解説
────────────────────────────────────
・ クラスの定義

 クラスの定義は、class と end で囲みます。

 class クラス名
  〜
 end

 です。

・ メソッドの定義

 メソッドの定義は、 def と end で囲みます。
 
 def メソッド名
  〜
 end

 初期化メソッドメソッド名は、initialize と決まっています。

 この初期化メソッドは、インスタンスの生成 ( new )の
 際に実行されます。
 
 メソッド名は、自由に決定します。
 ここでは、文字列出力メソッドとして println を定義

 メソッドを決めて、その中の処理を記述してゆくのが、
 Ruby プログラミング

・ インスタンス変数の定義
 @hoge = fuga

クラスを使うと何がうれしいのかは、「継承」をつかって
「差分プログラミング」ができることです。

ここでは、これ以上深入りしません。

また RubyCGI の詳しい説明は、liloのメンバーの堀川氏の最新の著作
『Ruby de CGI』が発売になったそうですので是非そちらを参照方。
http://www2.airnet.ne.jp/pak04955/book.html

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■   Ruby実践活用 Linux + Apache + Ruby/eRuby + PostgreSQL
       での メンバ制掲示板『mbbs』紹介

────────────────────────────────────
【1】  環境整備
────────────────────────────────────
1.1 OS
────────────────────────────────────
・ Linuxインストール
  (http://lilo.linux.or.jp/など参照 (^-^v)

  私は、以下で実行
   Vine 2.1.5
   RedHat 7.2

   Windows でも可能( Internetにさらす準備があれば :-p )
   

────────────────────────────────────
1.2 WebServer
────────────────────────────────────
・ Apacheインストール(2002/05/22現在 1.3.24)
  apache_1.3.24 のインストール
  (http://www.apache.jp/など参照)

	# cd /usr/local/src
	# tar xvfz apache_1.3.24.tar.gz
	# cd apache_1.3.24
	# OPTIM="-O2" ./configure --enable-module=so
	# make
	# make install

────────────────────────────────────
・ Apache設定ファイル編集

	# vi /usr/local/apache/conf/httpd.conf

	次の設定を追加・確認します。

	# ServerName を定義
	ServerName fsys

	# cgi で .rbx .rhtml を動作
	AddHandler cgi-script .rbx .rhtml

	# CGIを実行させたいディレクトリ領域に、オプション「ExecCGI」を追加
	  
	    order allow,deny
	    allow from all
	    Options +ExecCGI +Includes
	  


  mod_ruby を使用する場合は、もうすこしいろいろ設定があります。
  ここでは、mod_ruby は使いません。

────────────────────────────────────
1.3 Ruby
────────────────────────────────────
・ Rubyインストール(2002/05/25現在 1.6.7)

    $ tar fxzv ruby-1.6.7.tar.gz
    $ cd ruby-1.6.7
    $ ./configure --prefix=/usr/local 
    $ make
    $ su -
    # make install
    # ldconfig

────────────────────────────────────
・ eRubyインストール(2002/05/25現在 0.9.8)
  
  http://www.ruby-lang.org/en/raa-list.rhtml?name=eruby
  から ダウンロードし、展開、コンパイル、インストール

    $ tar fxzv eruby-0.9.8.tar.gz
    $ cd eruby-0.9.8
    $ ./configure.rb --with-charset=euc-jp
    $ make
    $ su -
    # make install
    # ldconfig

───────────────────────────────────
1.4 DB
────────────────────────────────────
・ PostgreSQLインストール(2002/05/25現在 7.2.1)
  (http://osb.sra.co.jp/PostgreSQL/7.2/install.htmlなども参照)
  
	# mkdir /usr/local/pgsql
	# chown postgres:postgres /usr/local/pgsql
	# cd /usr/local/src/
	# tar xzvf postgresql-7.2.1.tar.gz 
	# chown postgres.postgres -R postgresql-7.2.1

	login:postgres  
	$ cd postgresql-7.2.1

	$ ./configure --enable-multibyte=EUC_JP --enable-syslog
	$ make 
	$ make install
	$ make check

───────────────────────────────────
・ 実行環境設定
	$ cd ~
	$ vi ./bashrc
	export PG=/usr/local/pgsql
	export PGCLIENTENCODING=EUC_JP
	export PGLIB=$PG/lib
	export PGDATA=$PG/data
	export PATH=$PATH:$PG/bin
	export MANPATH="$MANPATH":$PG/man
	export LD_LIBRARY_PATH="$LD_LIBRARY_PATH":"$PGLIB"

	・ 変更を反映させる
	$ source .bashrc

	・ DB初期化
	$ initdb -E EUC_JP

	・ 設定
	$ cd /usr/local/pgsql/data
	$ chmod 600 pg_hba.conf
	$ vi pg_hba.conf
	host     all    192.168.1.0    255.255.255.0  trust


	$PGDATAディレクトリにあるpostgresql.confを編集する
	$ vi postgresql.conf
	fsync = false
	tcpip_socket = true
	silent_mode = true
	syslog = 2

	・ データベースの起動
	$ pg_ctl start

	・ 立ち上げ時の自動起動
	# vi /etc/rc.d/init.d
	su - postgres -c "/usr/local/pgsql/bin/postmaster -S -i"


	# createuser www
	y
	n

───────────────────────────────────
1.5 Ruby-PostgreSQL
───────────────────────────────────
 Ruby から PostgreSQL にアクセスするための拡張ライブラリも
 インストールする必要があります。

    ・ ダウンロード
    http://webclub.kcom.ne.jp/mb/noborus/ruby/index-ja.htmlRuby-PostgreSQLインストール(2002/05/25現在 0.6.5)

    $ tar fxzv ruby-postgres-0.6.5.tar.gz
    $ cd ruby-postgres-0.6.5

    ・  extconf.rb を実行。
       --with-pgsql-lib-dir と --with-pgsql-include-dir
       でPostgreSQL のライブラリ及びインクルードファイルがインストール
       されている場所を指定

    $ ruby extconf.rb --with-pgsql-lib-dir=/usr/local/pgsql/lib \
                      --with-pgsql-include-dir=/usr/local/pgsql/include

    $ make
    $ su -
    # make install

────────────────────────────────────
【2】 会員制掲示板 プロローグ
────────────────────────────────────
2.1 開発の動機
 
  ・ 現在高2の息子が、「現役生限定で親睦を深められるような掲示板が
     欲しい」と希望した。
  ・ はじめは、息子の CGI プログラミング練習におつきあい
  ・ どおせ やるなら Perl より Ruby の方が美しい
     ( Perlの $... にはウンザリ )
  ・ 「 RubyCGI 」本 でスタート
  ・ その本での事例では、掲示板は、データストアは、ファイルとPstore利用
  ・ 私は、PostgreSQL が お気に入りなので「メンバー管理にはDBを」と決め
     ていた。

2.2 開発の経過

  ・ 冬休み(当時は高1)の 親子の共同作業(=遊び)に3が日を返上
  ・ まず 会員登録部のみ Ruby-PostgreSQL連携で作成
  ・ 掲示板は、まず Ruby標準 ただただしさんのRuBBSでスタート
     (http://www.ruby-lang.org/en/raa-list.rhtml?name=RuBBS)

2.3 改善点

  ・ 携帯のユーザに使いやすいシンプルなフォーマットにしたい。
     高校生には、PCより携帯の方が普及。
     → 出力文字コードは、Shift_JIS

     (Tomcat(JSP),PostgreSQL連携では、EUCだと入出力OKだったが
      Shift_JISでは、文字化けを解消できず あきらめた思いであり)
     
  ・ ログオンでの ID を維持
     → cookie を使わないで Session を維持する方式が不可欠。
     
     Sessionの維持のため、はじめ生のIDを URL に見える形式で受け渡しに
     使って、簡単にIDを書き換えられた
     → MD5 ( 1方向ハッシュアルゴリズム )の組み込み
     
  ・ 掲示板を2段階方式で階層化
        トップメニューでは、たかだか10行程度にしたい(携帯)が
        掲示板の数は、数十(クラス別や、クラブ別)あるので必然的
        に階層化の実装を余儀なくされる

        掲示板の数がたくさんあると、出回っている掲示板では
        管理が大変
     

────────────────────────────────────
【3】 会員制掲示板 エッセンス
────────────────────────────────────
3.1 DB構造
────────────────────────────────────

・ mbbs.sql ファイルにて定義

   mbbs.sql は /var/fsys/mbbs/misc に配置

    • ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    • Member掲示板 DB定義
    • @author 福井(fukui@fsys.net)
    • 2002/01/02 fukui@fsys 設計開始
    • 2002/02/17 fukui@fsys #01 掲示板追加
    • $Id:
    • ━━━━━━━━━ テーブル一覧 abc順 ━━━━━━━━━━━━━━
    • bbslist 掲示板一覧 #01
    • const 定数
    • clublist 所属リスト
    • contents 書き込み内容 #01
    • maillist メールリスト
    • userlist Memberリスト
    • ━━━━━━━━━ Member情報━━━━━━━━━━━━━━━━━━━
    • ─────────────────────────────────
create table userlist -- ■ Memberリスト
    • Member一覧
    • ───────────────────────────── #
( id serial primary key, --自動連番 uid int4 UNIQUE, --MemberID
    • loginid text UNIQUE, --ログイン用ID (uid のいずれか)
pwd text, --パスワード(MD5ハッシュ値) name text, --氏名 lname text, --氏 fname text, --名 kname text, --氏名 カナ klname text, --氏 カナ kfname text, --名 カナ hname text, --handlename ktel text, --携帯電話番号 selfin text, --自己紹介 hobby text, --趣味 birthday text, --生年月日 9999(西暦)+MM+DD jhs text, --所属 etc1 int4, --各種管理用1 etc2 int4, --各種管理用2 memo text, --備考 valid int default 1, --1:有効 regid int4, --初期登録者 regdate Timestamp default text 'now', --初期登録日 upid int4, --更新者 updata Timestamp default text 'now' --更新日 ); grant all on userlist to www; grant all on userlist_id_seq to www; :
    • ───────────────────────────────────
create table maillist -- ■ メールリスト
    • メール一覧
    • メールアドレスは、複数なので別管理
    • ───────────────────────────── #
( id serial primary key, --自動連番 uid int4, --MemberID mail text, --メールアドレス memo text, --備考 valid int default 1, --1:有効 regid int4, --初期登録者 regdate Timestamp default text 'now', --初期登録日 upid int4, --更新者 updata Timestamp default text 'now' --更新日 ); grant all on maillist to www; grant all on maillist_id_seq to www;
    • ───────────────────────────────────
create table grouplist -- ■ グループ一覧
    • グループ一覧
    • ───────────────────────────── #01
( gid serial primary key, --グループID 自動連番 gname text, --グループ名 gsname text, --グループ略称 uid int4, --管理者ID etc1 int4, --各種管理用1 etc2 int4, --各種管理用2 memo text, --備考 valid int default 1, --1:有効 regid int4, --初期登録者 regdate Timestamp default text 'now', --初期登録日 upid int4, --更新者 updata Timestamp default text 'now' --更新日 ); grant all on grouplist to www; grant all on grouplist_gid_seq to www; vacuum grouplist;
    • ───────────────────────────────────
create table bbslist -- ■ 掲示板一覧
    • 掲示板一覧
    • ───────────────────────────── #01
( bid serial primary key, --掲示板ID 自動連番 bname text, --掲示板名 bsname text, --掲示板略称 gid int4, --グループID uid int4, --管理者ID etc1 int4, --各種管理用1 etc2 int4, --各種管理用2 memo text, --備考 valid int default 1, --1:有効 regid int4, --初期登録者 regdate Timestamp default text 'now', --初期登録日 upid int4, --更新者 updata Timestamp default text 'now' --更新日 ); grant all on bbslist to www; grant all on bbslist_bid_seq to www; vacuum bbslist; :
    • ───────────────────────────────────
create table contents -- ■ 書き込み内容
    • 書き込み内容
    • ───────────────────────────── #
( cid serial primary key, --書き込みID 自動連番 uid int4, --MemberID (現状はuid参照)
    • id int4, --Member自動連番 (loginid使用時)
bid int4, --掲示板ID bno int4, --掲示板内連番 pcid int4, --親contentsID title text, --タイトル cont text, --書き込み内容 etc1 int4, --各種管理用1 etc2 int4, --各種管理用2 memo text, --備考 valid int default 1, --1:有効 regid int4, --初期登録者 regdate Timestamp default text 'now', --初期登録日 upid int4, --更新者 updata Timestamp default text 'now' --更新日 ); grant all on contents to www; grant all on contents_cid_seq to www; ──────────────────────────────────── 3.2 DB実装 ──────────────────────────────────── DBの登録を行う ・ データベース定義 DB create postgres(PostgreSQLスーパユーザ)でログイン $ createdb mbbs mbbsデータベースが確保できたか確認 $ psql -l List of databases Database | Owner | Encoding -----------+----------+---------- mbbs | postgres | EUC_JP template0 | postgres | EUC_JP template1 | postgres | EUC_JP : ・ テーブル定義 create table sql文を、読み込ませる(実行する) $ cd /var/fsys/mbbs/misc $ psql mbbs < mbbs.sql 定義されたテーブルを確認 $ psql mbbs テーブルの定義状態の確認は \d コマンド mbbs=#\d List of relations Name | Type | Owner --------------------+----------+---------- bbslist | table | postgres bbslist_bid_seq | sequence | postgres clublist | table | postgres clublist_id_seq | sequence | postgres const | table | postgres contents | table | postgres contents_cid_seq | sequence | postgres grouplist | table | postgres grouplist_gid_seq | sequence | postgres jhslist | table | postgres log | table | postgres log_id_seq | sequence | postgres maillist | table | postgres maillist_id_seq | sequence | postgres msg | table | postgres newlist | table | postgres userlist | table | postgres userlist_id_seq | sequence | postgres viewlog | table | postgres viewlog_viewid_seq | sequence | postgres vuserlist | view | postgres (21 rows) ──────────────────────────────────── 3.3 スクリプト実装 ──────────────────────────────────── ────────────── ・ スクリプトの配置 ────────────── スクリプトの置き場は、次のようにします /var +-fsys | +-mbbs | +-cgi ここに スクリプト本体 .rhtmlファイル | +-lib ここには、共通ライブラリなど | +-misc ここには、各種 .sqlファイルなど ────────────── ・ Apacheに配置場所を指示 ────────────── Apacheの DocumentRoot は,通常 /usr/local/apache/htdocs なので、そこからリンクを張ります。 $ cd /usr/local/apache/htdocs $ sudo ln -s /var/fsys/mbbs mbbs ────────────── ・ ログインの入り口 ────────────── では、ようやく 会員制掲示板 mbbs を覗いてみましょう。 /var/fsys/mbbs に index.html を設置 inputタグ内の istyle オプションで、携帯の入力モードを指定 この場合 "4"は、数字を指定しています。 Login mbbs
ID
Password

form のアクション(loginクリック)で /cgi/login.rbx を呼び出します。 ────────────── ・ ログインデータのチェック ────────────── まず 拡張子 .rbx は、生Ruby スクリプトです。 DBに登録されているIDやパスワードと照合チェックします。 md5 の ハッシュを利用しているのがポイントです。 #!/usr/local/bin/ruby -Ke =begin #-------------------------------------------------------------------------- = login.rbx ログインの受付 =end #-------------------------------------------------------------------------- # @version A00 2002/01/02 福井@Fsys orignal # @version A00 2002/05/25 福井@Fsys LMS向けModify #-------------------------------------------------------------------------- require 'cgi' require 'md5' require '../lib/util' require '../lib/pgCon' cgi = CGI.new uid = cgi['uid'][0].to_s password = cgi['pwd'][0].to_s.MD5.new(uid).hexdigest db = DBConnection.new db.connect id,name,userPassword = \ db.query("select id,name,pwd from userlist where uid='#{uid}';")[0] if password == userPassword then sid = MD5.new(uid).hexdigest + id.to_s db.close cgi.out('status' => 'REDIRECT', 'Location' => cgi.script_absolute_uri_base + 'group.rhtml?sid='+sid){""} else db.close cgi.out('status' => 'REDIRECT', 'Location' => cgi.script_absolute_uri_base ) { "" } end ─────────────────── ・ PostgreSQLアクセス wrapper class ─────────────────── PostgreSQLアクセスの共通クラスをご覧ください。 DBアクセスの為の id,パスワード は、このライブラリで、一カ所で のみ定義します。 connection 維持中であれば、新たに connection しません。 # original by Ueno # @version A00 2002/05/25 fukui@fsys LMS向けModify =begin = pgCon.rb PostgreSQL との接続を透過的に行うためのクラス。 =end require 'postgres' =begin == DBConnection PostgreSQL との接続を行うクラス。 === Class Methods:
      • DBConnection.new
DBConnection オブジェクトを作成する。 === Methods:
      • DBConnection#connect
データベースに接続する。ブロックが与えられた場合は、 自分自身を与えてブロックを実行し、その後接続を切断する。 認証に失敗している場合は、データベースに接続せずに 処理を続行する。
      • DBConnection#transaction
データベースに接続し、与えられたブロックをトランザクションとして 実行する。
      • DBConnection#rollback
トランザクション中でロールバックを行う。
      • DBConnection#close
データベースとの接続を切断する。
      • DBConnection#query(sql)
データベースに SQL 文を送り、実行した結果を配列で返す。 認証に失敗している場合は、データベースに SQL 文を送らず、 常に空の配列を返す。 =end class DBConnection class Error < StandardError ; end def self.escape(s) s.gsub(/'/,"''") end def self.like_escape(s) s = s.gsub(/'/,"''") s.gsub!(/\\/,"\\\\") s.gsub!(/%/,"\\%") s.gsub!(/_/,"\\_") s end def initialize @connect = nil @in_transaction = false end private def pgconnect PGconn.connect "localhost", 5432, "", "", "mbbs", "www", "??????" end def check_connection raise Error, "DB connection is not established" unless @connect end public def connect connected = false if @connect then connected = true else @connect = pgconnect end unless block_given? then self else begin yield self ensure close if not connected and @connect end end end class Rollback < StandardError ; end def rollback raise "not in transaction" unless @in_transaction raise Rollback, "rollback" end def transaction connect { raise "already in transaction" if @in_transaction @in_transaction = true commit = 'rollback;' ret = nil query 'begin;' begin ret = yield(self) commit = 'commit;' rescue Rollback # do nothing ensure @in_transaction = false query commit end ret } end def close raise "in transaction" if @in_transaction check_connection begin @connect.close ensure @connect = nil end end class DuplicateKeyError < PGError ; end class ConstraintError < PGError ; end def query(sql) check_connection STDERR.puts "SQL> " + sql if $DEBUG begin @connect.query sql rescue PGError => e case e.message when /\AERROR:\s*Cannot insert a duplicate key/ then raise DuplicateKeyError, e.message when /\AERROR:.*referential integrity violation/ then raise ConstraintError, e.message else raise e end end end end ─────────────────── ・ Portalページ (グループメニュー) ─────────────────── では、メンバ制掲示板のポータル画面を見てみます。 Defaultコードは EUC。 出力はShift_JIS コード変換は、nkf eRuby 方式で HTML文書の中に <% 〜 %> で Ruby スクリプトを埋め込みます。 nkfは、ネットワーク用漢字コード変換フィルタで、Rubyに標準添付。 詳細は ftp://ftp.ie.u-ryukyu.ac.jp/pub/software/kono/nkf171.shar ・ 多重代入 演算子= の左辺がカンマで区切ったリストになっている場合、 「多重代入」と呼ばれて、複数の代入を一度に行う。 #!/usr/local/bin/eruby -Ke -C Shift_JIS <% =begin #-------------------------------------------------------------------------- = grouplist.rhtml グループ一覧 グループ一覧の表示と選択の受付 =end #-------------------------------------------------------------------------- # @version A00 2002/02/16 福井@Fsys Original # @version A00 2002/03/22 福井@Fsys 共通定数化 # @version A00 2002/05/25 福井@Fsys LMS向けModify #-------------------------------------------------------------------------- require 'cgi' require 'nkf' require '../lib/pgCon' require '../lib/util' require 'kconv' require 'mbbs' title = Mbbs::TOPTITLE cgi = CGI.new sid = cgi['sid'][0].to_s search = cgi['search'][0].to_s.toeuc #SessionIDを判定。不正ならログイン画面へ sido = "#{sid}" db = DBConnection.new db.connect if sido if sido.length() < 33 cgi.redirect 'index.rhtml?mid=4' end sid2 = sido.slice!(0,32) id = sido.to_i uid,name = db.query("select uid,name from userlist where id=#{id};")[0] #ハッシュ値チェック unless sid2.to_s == MD5.new(uid).hexdigest cgi.redirect 'index.rhtml?mid=5' end else cgi.redirect 'index.rhtml?mid=6' end maxc = db.query("select max(cid) from contents")[0] maxnum = maxc[0] newc = db.query("select cid,shorttime(updata) " \ "from contents where bid != 100 order by updata DESC limit 1")[0] newcid,newtime = newc newb = db.query("select bid from contents where cid=#{newcid}")[0] bid = newb[0] bbs = db.query("select bname,bsname,gid,etc1 " \ "from bbslist where bid=#{bid};")[0] bname,bsname,gid,bnum = bbs grouplist = db.query("select gid,gname,gsname " \ "from grouplist order by updata DESC ") %> <%=<a class="keyword" href="http://d.hatena.ne.jp/keyword/NKF">NKF</a>.<a class="keyword" href="http://d.hatena.ne.jp/keyword/nkf">nkf</a>('-sZ',title)%> <%=NKF.nkf('-sZ',title)%> (<%=NKF.nkf('-sZ',maxnum)%>)
<%=NKF.nkf('-sZ',bname)%> (<%=NKF.nkf('-sZ',bnum)%>) <%=NKF.nkf('-sZ','が最新! ')%> <%= newtime %>
<% gno = 0 grouplist.each{ |group| gid,gname,gsname = group gno = gno + 1 %> &sid=<%=sid%>> (<%=gno%>) <%=NKF.nkf('-sZ',gname)%><%=NKF.nkf('-sZ',gsname)%>
<% } %>

[1]<%=NKF.nkf('-sZ','本人情報')%>
[2]<%=NKF.nkf('-sZ','Member一覧')%>

<%=NKF.nkf('-sZ','Member掲示板について')%>

─────────────────── ・ 内容表示・入力ページ (中核部) ─────────────────── #!/usr/local/bin/eruby -Ke -C Shift_JIS <% =begin #-------------------------------------------------------------------------- = bbs.rhtml 掲示板 掲示板の表示と掲示板の登録、更新、削除の受付 =end #-------------------------------------------------------------------------- # @version A00 2002/02/16 福井@Fsys Original # @version A00 2002/02/23 福井@Fsys # @version A00 2002/05/25 福井@Fsys LMS向け Modify #-------------------------------------------------------------------------- require 'cgi' require 'nkf' require '../lib/pgCon' require '../lib/util' require 'kconv' require 'mbbs' title1 = Mbbs::TOPTITLE member = Mbbs::MEMNAME cgi = CGI.new bid = cgi['bid'][0].to_s sid = cgi['sid'][0].to_s soffset = cgi['offset'][0].to_s slimit = cgi['limit'][0].to_s direc = cgi['direc'][0].to_s sbno = cgi['bno'][0].to_s start = cgi['start'][0].to_s search = cgi['search'][0].to_s.toeuc bno = nil sech = ' ' ' 各種チェック処理 省略 db = DBConnection.new db.connect bbs = db.query("select bname,bsname,gid,etc2,valid " \ "from bbslist " \ "where bid="+bid+";") bname,bsname,gid,etc2,valid = bbs[0] group = db.query("select gname from grouplist " \ "where gid="+gid+";")[0] unless sbno.empty? offset = sbno.to_i end contents = db.query("select cid,bno,title,cont,shorttime(c.updata), " \ "shorttime(c.regdate),hname,c.uid,c.etc2 " \ "from contents as c " \ "left outer join userlist as u on c.uid = u.uid " \ "where bid=#{bid} and c.valid=1 #{sech} " \ "order by c.regdate #{direc} limit #{limit} offset #{offset};") %> <%=<a class="keyword" href="http://d.hatena.ne.jp/keyword/NKF">NKF</a>.<a class="keyword" href="http://d.hatena.ne.jp/keyword/nkf">nkf</a>('-sZ',title1)%> > <%=NKF.nkf('-sZ',title1)%> &sid=<%=sid%>> <%=NKF.nkf('-sZ',group[0])%>> <%=NKF.nkf('-sZ',bname)%> : <% contents.each{ |record| cid,bno,title,cont,updata,regdate,hname,cuid,cnum = record : %>
&sid=<%=sid%>> [<%=bno%>] : <%=regdate%> <%=NKF.nkf('-sZ',hname)%> (<%=cnum unless nil%>) <%=link2%> &sid=<%=sid%>> <%=NKF.nkf('-sZ',title)%> <% if limit < 6 %>
<% end%> <%=NKF.nkf('-sZ',cont)%> <% } %> : <%=NKF.nkf('-sZ','Title')%> <%=NKF.nkf('-sZ','内容')%> ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ■■ まとめ ──────────────────────────────────── ・ Ruby では、「 たのしいプログラミング 」が味わえます。 少なくとも 私が経験してきた FORTRAN, PCL/SPL(HIDIC制御用),LISP, c, N88BASIC, VisualBasic, VBScript, JavaScript, Java/JSP, Perl(ちょっとだけ),PHP(言語に分類するのは正しくなく、ひとつ のアプリケーションの中のスクリプトなのですが)の中では、間違 いなくBEST な言語だと思います。 とくに イテレータ はすばらしいです。 つかいやすくて強力!エレガントなオブジェクト指向言語! ・ 今後のWebサービス(SOAP/WADL/UDDI)では、Java と VS.NET が 主導権争いを演じてくれるわけですが、 RubyGarden.orgでは、 「SOAP/WSDL/UDDIを合わせてサポートして、RubyをキラーWeb Service言語に」と言ってる人もいます。 http://www.rubygarden.org/comments.php?op=showreply&tid=41&sid=72&pid=40&mode=threaded&order=0&thold=0#41 文字列のハンドリングには、どの言語が優れているかは、 明らかです。(補足参照) ・ Linux + Ruby + PostgreSQL + Apache ... とオープンソースの すばらしい道具達と どんどん進化しているPCやインターネットの インフラにめぐまれている今 作る人(エンジニア)は、使う人(ユーザ)のニーズに応えて どんどん 創作 してゆける 幸せを味わってゆきたいものです。 (いろいろしんどいこともあるけど^^) ・ たかが掲示板、されど掲示板 DBをデータストアに使用することで、並べ替えや、検索、などファイル をデータストアにする方式に比べていろいろな機能が、実装しやすくなる ということは言えるでしょう。 家具(道具)をつくる程度の取り組みで、はじめましたが、いろいろ やっているうちに、家を建てるぐらいのことには、広がってゆくなあ という感想をもちます。 狭い意味の掲示板もあるが、広い意味の掲示板は、情報システムそのもの! また 掲示板は、記録がのこるチャットとしての利用もあり.. ・ インターネットで、ひととひとが繋がる メールのおかげで lilo .... があり また メール以外のコミュニティ(チャットとか2ちゃんねるとか) も いろいろ存在します。 ・ ひとつの高校での実践(2週間で1万件を越える書込。さすが若者パワー) で一定の成果が得られました。 ・ 今後は、このツールをもうすこし磨きをかけて 公開し、皆様に使って もらえたらなあ と考えています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ■■ 補足 郵便番号データのハンドリング ─────────────────────────────────── 例えば、今からhttp://www.post.yusei.go.jp/newnumber/から 郵便番号簿をダウンロードして、検索するシステムを構築すること を想定してみてください。 全国一括で約 1,7MB 12万件程あります。この ken_all.lzh を ダウンロードして解凍します。すると11.4MB の KEN_ALL.CSV が 出来上がります。↓新郵便番号データの説明 http://www.post.yusei.go.jp/newnumber/readme.htm まあこの量だとファイルでそのまま扱うよりDBに入れて利用する のが正解でしょう。 解凍したファイルは、SHIFT_JISコードで、Linux上のPostgreSQLは、 通常 EUCコードを用いるので、コード変換が必要となります。 また インターネット上で扱うことも考慮すると半角カナも全角カナ に変換します。 さらに PostgreSQL でテーブルに流しこんでやるのに便利な copy コマンドが使えるのですが、その場合 Defaultでは、区切り文字は ダブルクォーテーションとカンマでなくタブなのです。 そこで、これらの変換を一気に掛けてやります。 #! /usr/local/bin/ruby -Ks require "nkf" ARGF.each do | line | line.gsub(/"/,'') line.gsub(/,/,'\t') print NKF.nkf('-XSe', line) end ARGFは、コマンドラインに並んだ文字列をファイル名と見なして 仮想IOオブジェクトとして扱える組み込み定数です。 例えば上記スクリプトを s2e.rb として $ ./s2e.rb KEN_ALL.CSV > ken_all.euc で実行して 変換後のファイルを作成します。 Javaでの半角カナ操作は、なかなかはまるものがありますし、 VS.NETは、今から購入しなければなりませんし..VSのC#を使うのか VB.NETをまず悩まなければなりません。 PHPだと ファイルの操作だけをするにも Webから呼んでやらないと いけないのですよね。