mongoDB使ってみた。


どんなもんだろうなーと思ったので試してみた。
環境はDebian Squeeze

インストール


Debianなので本家サイトのやり方に従ってパッケージで入れる。


キー追加。

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10


リポジトリ追加。

$ sudo vi /etc/apt/sources.list


行末に追加する。

deb http://downloads-distro.mongodb.org/repo/debian-sysvinit dist 10gen


インストール。

$ sudo apt-get update 
$ sudo apt-get install mongodb-10gen


動作確認でシェル実行。

$ mongo
MongoDB shell version: 1.8.1
connecting to: test

INSERT的なこと


db.<テーブル名的な>.insert(<保存データJSON>)でできるっぽい。
テーブル作る的なことはしなくてもデータ格納時になければ勝手に作ってくれるみたい。

データ保存する
> db.users.insert({ name : 'test1', seq : 1});
いっぱい突っ込む
> for (var i = 2; i <= 100; i++) db.users.insert({ name : 'test' + i, seq : i})

SELECTする

SELECT * FROM users みたいな
> db.users.find();
{ "_id" : ObjectId("4da636ce57311e95d38bd011"), "name" : "test1", "seq" : 1 }
{ "_id" : ObjectId("4da637af57311e95d38bd012"), "name" : "test2", "seq" : 2 }
{ "_id" : ObjectId("4da637af57311e95d38bd013"), "name" : "test3", "seq" : 3 }
{ "_id" : ObjectId("4da637af57311e95d38bd014"), "name" : "test4", "seq" : 4 }
{ "_id" : ObjectId("4da637af57311e95d38bd015"), "name" : "test5", "seq" : 5 }
{ "_id" : ObjectId("4da637af57311e95d38bd016"), "name" : "test6", "seq" : 6 }
{ "_id" : ObjectId("4da637af57311e95d38bd017"), "name" : "test7", "seq" : 7 }
{ "_id" : ObjectId("4da637af57311e95d38bd018"), "name" : "test8", "seq" : 8 }
{ "_id" : ObjectId("4da637af57311e95d38bd019"), "name" : "test9", "seq" : 9 }
{ "_id" : ObjectId("4da637af57311e95d38bd01a"), "name" : "test10", "seq" : 10 }
{ "_id" : ObjectId("4da637af57311e95d38bd01b"), "name" : "test11", "seq" : 11 }
{ "_id" : ObjectId("4da637af57311e95d38bd01c"), "name" : "test12", "seq" : 12 }
{ "_id" : ObjectId("4da637af57311e95d38bd01d"), "name" : "test13", "seq" : 13 }
{ "_id" : ObjectId("4da637af57311e95d38bd01e"), "name" : "test14", "seq" : 14 }
{ "_id" : ObjectId("4da637af57311e95d38bd01f"), "name" : "test15", "seq" : 15 }
{ "_id" : ObjectId("4da637af57311e95d38bd020"), "name" : "test16", "seq" : 16 }
{ "_id" : ObjectId("4da637af57311e95d38bd021"), "name" : "test17", "seq" : 17 }
{ "_id" : ObjectId("4da637af57311e95d38bd022"), "name" : "test18", "seq" : 18 }
{ "_id" : ObjectId("4da637af57311e95d38bd023"), "name" : "test19", "seq" : 19 }
{ "_id" : ObjectId("4da637af57311e95d38bd024"), "name" : "test20", "seq" : 20 }
has more
SELECT * FROM users LIMIT 5 みたいな
> db.users.find().limit(5)
{ "_id" : ObjectId("4da638a757311e95d38bd075"), "name" : "test1", "seq" : 1 }
{ "_id" : ObjectId("4da638a757311e95d38bd076"), "name" : "test2", "seq" : 2 }
{ "_id" : ObjectId("4da638a757311e95d38bd077"), "name" : "test3", "seq" : 3 }
{ "_id" : ObjectId("4da638a757311e95d38bd078"), "name" : "test4", "seq" : 4 }
{ "_id" : ObjectId("4da638a757311e95d38bd079"), "name" : "test5", "seq" : 5 }
SELECT * FROM users WHERE seq = 5 みたいな
> db.users.find({seq : 5})
{ "_id" : ObjectId("4da638a757311e95d38bd079"), "name" : "test5", "seq" : 5 }
SELECT name FROM users WHERE seq = 10 みたいな
> db.users.find({seq : 10}, {name : true})
{ "_id" : ObjectId("4da638a757311e95d38bd07e"), "name" : "test10" }
SELECT * FROM users WHERE seq > 5 AND seq < 10 みたいな
> db.users.find({ seq : { $gt : 5, $lt : 10}})
{ "_id" : ObjectId("4da638a757311e95d38bd07a"), "name" : "test6", "seq" : 6 }
{ "_id" : ObjectId("4da638a757311e95d38bd07b"), "name" : "test7", "seq" : 7 }
{ "_id" : ObjectId("4da638a757311e95d38bd07c"), "name" : "test8", "seq" : 8 }
{ "_id" : ObjectId("4da638a757311e95d38bd07d"), "name" : "test9", "seq" : 9 }
SELECT * FROM users WHERE seq >= 5 AND seq <= 10 みたいな
> db.users.find({ seq : { $gte : 5, $lte : 10}})
{ "_id" : ObjectId("4da638a757311e95d38bd079"), "name" : "test5", "seq" : 5 }
{ "_id" : ObjectId("4da638a757311e95d38bd07a"), "name" : "test6", "seq" : 6 }
{ "_id" : ObjectId("4da638a757311e95d38bd07b"), "name" : "test7", "seq" : 7 }
{ "_id" : ObjectId("4da638a757311e95d38bd07c"), "name" : "test8", "seq" : 8 }
{ "_id" : ObjectId("4da638a757311e95d38bd07d"), "name" : "test9", "seq" : 9 }
{ "_id" : ObjectId("4da638a757311e95d38bd07e"), "name" : "test10", "seq" : 10 }
SELECT * FROM users WHERE name LIKE "test1%" みたいな
> db.users.find({name : /test1.*/i})
{ "_id" : ObjectId("4da638a757311e95d38bd075"), "name" : "test1", "seq" : 1 }
{ "_id" : ObjectId("4da638a757311e95d38bd07e"), "name" : "test10", "seq" : 10 }
{ "_id" : ObjectId("4da638a757311e95d38bd07f"), "name" : "test11", "seq" : 11 }
{ "_id" : ObjectId("4da638a757311e95d38bd080"), "name" : "test12", "seq" : 12 }
{ "_id" : ObjectId("4da638a757311e95d38bd081"), "name" : "test13", "seq" : 13 }
{ "_id" : ObjectId("4da638a757311e95d38bd082"), "name" : "test14", "seq" : 14 }
{ "_id" : ObjectId("4da638a757311e95d38bd083"), "name" : "test15", "seq" : 15 }
{ "_id" : ObjectId("4da638a757311e95d38bd084"), "name" : "test16", "seq" : 16 }
{ "_id" : ObjectId("4da638a757311e95d38bd085"), "name" : "test17", "seq" : 17 }
{ "_id" : ObjectId("4da638a757311e95d38bd086"), "name" : "test18", "seq" : 18 }
{ "_id" : ObjectId("4da638a757311e95d38bd087"), "name" : "test19", "seq" : 19 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d8"), "name" : "test100", "seq" : 100 }
SELECT * FROM users ORDER seq DESC みたいな
> db.users.find().sort({ seq : -1 })
{ "_id" : ObjectId("4da638a757311e95d38bd0d8"), "name" : "test100", "seq" : 100 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d7"), "name" : "test99", "seq" : 99 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d6"), "name" : "test98", "seq" : 98 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d5"), "name" : "test97", "seq" : 97 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d4"), "name" : "test96", "seq" : 96 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d3"), "name" : "test95", "seq" : 95 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d2"), "name" : "test94", "seq" : 94 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d1"), "name" : "test93", "seq" : 93 }
{ "_id" : ObjectId("4da638a757311e95d38bd0d0"), "name" : "test92", "seq" : 92 }
{ "_id" : ObjectId("4da638a757311e95d38bd0cf"), "name" : "test91", "seq" : 91 }
{ "_id" : ObjectId("4da638a757311e95d38bd0ce"), "name" : "test90", "seq" : 90 }
{ "_id" : ObjectId("4da638a757311e95d38bd0cd"), "name" : "test89", "seq" : 89 }
{ "_id" : ObjectId("4da638a757311e95d38bd0cc"), "name" : "test88", "seq" : 88 }
{ "_id" : ObjectId("4da638a757311e95d38bd0cb"), "name" : "test87", "seq" : 87 }
{ "_id" : ObjectId("4da638a757311e95d38bd0ca"), "name" : "test86", "seq" : 86 }
{ "_id" : ObjectId("4da638a757311e95d38bd0c9"), "name" : "test85", "seq" : 85 }
{ "_id" : ObjectId("4da638a757311e95d38bd0c8"), "name" : "test84", "seq" : 84 }
{ "_id" : ObjectId("4da638a757311e95d38bd0c7"), "name" : "test83", "seq" : 83 }
{ "_id" : ObjectId("4da638a757311e95d38bd0c6"), "name" : "test82", "seq" : 82 }
{ "_id" : ObjectId("4da638a757311e95d38bd0c5"), "name" : "test81", "seq" : 81 }
has more

UPDATEする

UPDATE users SET name = "test555" WHERE seq = 5 みたいな
> db.users.update({seq : 5}, { $set : { name : 'test555'}})
> db.users.find({seq : 5})                                 
{ "_id" : ObjectId("4da63d9557311e95d38bd0dd"), "name" : "test555", "seq" : 5 }

DELETEする

DELETE FROM users WHERE seq = 5 みたいな
> db.users.remove({seq : 5})
DELETE FROM users みたいな
> db.users.remove()

シェルの終了

> exit

感想


JavaScriptチックなせいか、なんだかとっても好感触。
これは好きになれそうかも。

node.js-v0.4.4のビルドでころんだ。


先日、node.jsの0.4.4が出てるのに気がついたので、
ダウンロードしてビルドしたら、

scons: *** [obj/release/api.o] Error 1
scons: building terminated because of errors.
Waf: Leaving directory `/home/alone/Downloads/node/node-v0.4.4/build'
Build failed:  -> task failed (err #2): 
	{task: libv8.a SConstruct -> libv8.a}
make: *** [program] エラー 1

ってな感じにエラーになって、眠かったのでその日は放置した。
環境はDebian SqueezeでGCCは4.4.5。


で、今日Googleのnodejsグループのディスカッションでそれっぽい話題見つけたので、
参考にリトライしてみた。


パッチ当てればよい的な感じなので、パッチをダウンロード。
アーカイブを展開して、node-v0.4.4/内に入れて、パッチあて。

$ patch < 0001-turn-off-strictaliasing-for-v8.patch


で、ビルド

$ make


うまく行ったみたいなので、インストールしてバージョン確認。

$ sudo make install
$ node --verison


v0.4.4が表示されたので前に作ったものとか動かしてみて確認。大丈夫っぽい。

SVG用のtoDataURL作ってみた(ただしChromeに限る)。


SVGにはCanvasみたいなデータスキーム作れるtoDataURLメソッドがないみたい。
まあ、DOMを文字列にしてBase64エンコードすればいいかなと思ってたら、
ふと、File API使ってできないもんかなーと思ったのでやってみた。


流れとしては、
SVGのDOMを文字列にする。

BlobBuilder使ってBlobにする。

FileReaderでDataURLとして読ませて、結果をとる。


で、書いてみたソース。

/**
 * Window Load Event
 */
window.addEventListener('load', function() {
    
    /**
     * Document
     */
    var doc = document;
    /**
     * SVG Namespace
     */
    var SVG_NS = 'http://www.w3.org/2000/svg';
    
    /**
     * SVG Root
     */
    var svg = doc.createElementNS(SVG_NS, 'svg');
    svg.setAttribute('viewBox', '0 0 200 200');
    svg.setAttribute('id', 'svg-1');
    svg.setAttribute('xmlns', SVG_NS);
    svg.setAttribute('version', '1.1');
    
    /**
     * Rect
     */
    var rect = doc.createElementNS(SVG_NS, 'rect');
    rect.setAttribute('id', 'svg-2');
    rect.setAttribute('width', 100);
    rect.setAttribute('height', 100);
    rect.setAttribute('fill', '#ff0000');
    svg.appendChild(rect);
    
    /**
     * Text
     */
    var text = doc.createElementNS(SVG_NS, 'text');
    text.textContent = '日本語';
    text.setAttribute('y', 50);
    text.setAttribute('fill', '#000000');
    text.setAttribute('font-size', '16px');
    svg.appendChild(text);
    
    // inline svg追加 
    doc.body.appendChild(svg);
    
    // toDataURL
    toDataURL(svg, function(scheme) {
        // 生成したデータスキームで画像描画
        var img = new Image();
        img.src = scheme;
        doc.body.appendChild(img);
    });
    
    /**
     * SVGをデータスキームに変換します。
     * 
     * @param {Object} svgRoot SVGのDOMルート
     * @param {Function} callback 結果取得コールバック
     */
    function toDataURL(svgRoot, callback) {
        // XMLシリアライザインスタンス生成
        var xs = new XMLSerializer();
        // DOMをシリアライズして文字列にする
        var xml = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' + xs.serializeToString(svgRoot);
        // BlobBuilerのインスタンス生成
        var bb = new BlobBuilder();
        // シリアライズした文字列をBlobにする。
        bb.append(xml);
        // ファイルリーダのインスタンス生成
        var fr = new FileReader();
        // ロード完了イベントのハンドラ設定
        fr.onloadend = function() {
            // コールバックに結果を渡す
            callback.call(null, 'data:image/svg+xml;' + fr.result.slice(5));
        };
        // BlobBuilderで作ったBlobをデータURLとして読み込み
        fr.readAsDataURL(bb.getBlob());
    }

}, false);


できたはできたけど、現時点でBlobBuilder対応してるのChromeだけなので、Chrome以外はアウト。
それにわざわざBlobにしてからデータスキームに変換するなら、世に出回ってるBase64エンコードライブラリ使って、
文字列から直接エンコードした方がいいんじゃね?と作ってから気がついた。


とりあえず、File APIはもっと対応進んでほしいなぁ。

IE9をとりあえず試せる環境作って試してみた。


IE9がリリースされましたが、XPにはインストールできないわけで、
Vistaも7も持ってねーよヽ(`д´)ノ ウワァァァンしてたわけですが、
そういえば評価版ってあったような?と思ったので試してみました。

Windows7の開発者向け評価版をダウンロードする。


MSDN Evaluation Center」の「Windows Server 2008 R2 SP1」のダウンロードページにいくと、
右側の関連ダウンロードに、「Windows 7 Enterprise 90 日間評価版」があるので、そこからダウンロードします。
(当初、Winsows Server 2008の評価版でやろうと思ってたら、ここでWin7の評価版見つけた)
なんか有効期限が2011年12月31日まで延長されたみたいですね。
ダウンロードの際には「Windows Live ID」が必要です。ISOイメージは2.2GB位でした。

VMWare使って仮想マシンにインストールする。


落としたISOを使って仮想マシン作ります。
評価版はプロダクトキーがないので、OSのインストールは後でやるやり方で作ります。
インストールが終わったら、Windows Updateします。SP1も当てます。VMWareのツールとかも入れます。

IE9をインストールする。


Beauty of the Webの右側にあるIE9の「Download Now」からダウンロードして、インストールします。

感想


VMWareで試してるので、速度的なところはよくわからないです。
F12で出てくるdeveloper toolのコンソールでさらっと試したところ、


JSON.stringifyとJSON.parseが入ったのが嬉しい。
addEventListenerが入ったのがすごく嬉しい。
ってな感じでした。

jsunitをAntで動かす

jsunitをAntで動かしてみる。
OSはVine Linux5.2でApacheは2.2が入ってる。
jsunitは既に使ってるバージョン2.2を使う前提。
/var/www/html/jsunitに入れてある。

Antの導入。


Apache AntのDownloadでバイナリのアーカイブを入手する。
2010年12月14日現在のバージョンは1.8.1。


アーカイブを展開して、/usr/local/libにリネームして設置。

# tar zxvf apache-ant-1.8.1-bin.tar.gz
# mv apache-ant-1.8.1 /usr/local/lib/apache-ant


ANT_HOMEと配下のbinをPATHに追加。

ANT_HOME=/usr/local/lib/apache-ant
PATH=$PATH:/var/lib/gems/1.8/bin:$ANT_HOME/bin

jsunitの設定


jsunitのディレクトリ配下にあるbuild.xmlをコピーして使う。
コピー先のディレクトリには直下にテストスイートファイルalltest.htmlが作ってある。


jsunitのホームディレクトリパスを独自にjsunitHomeプロパティとして追加する。

<property name="jsunitHome" value="/var/www/html/jsunit" description="jsunit home dir" />


とりあえず、テストに使うブラウザはFirefoxなので、
jsunit付属の起動・終了シェルスクリプトを使う。
Firefoxのプロファイルも通常使っているままにする。
browserFileNamesのvalue値を設定する。

<property
    name="browserFileNames"
    value="${jsunitHome}/bin/unix/start-firefox.sh;${jsunitHome}/bin/unix/stop-firefox.sh"
    description="browserFileNames is a comma-separated list of browsers in which to run tests when StandaloneTest is invoked on this host. Each value can be a semi-colon separated list, with the second value being the program to run to shut down the browser and the third value being the display name of the browser. The second and third values are optional. For a JsUnit Server, this is a mandatory property. For example: 'c:\program files\internet explorer\iexplore.exe;c:\program files\killie.bat;Internet Explorer 6.0,c:\program files\netscape\netscape7.1\netscp.exe,c:\program files\Opera\runopera.bat'"
    />


8080番を別に使っているので、ポート番号を変更する。
portのvalue値を設定する。

<property
    id="port"
    name="port"
    value="10000"
    description="port is the port on which the JsUnitStandardServer runs. This is not a mandatory property. If not specified, 8080 is assumed. For exapmle: '8080'"
    />


テスト実行URLを設定する。
テストランナーにalltest.htmlを指定して自動実行させる。
urlのvalue値を設定する。

<property
    id="url"
    name="url"
    value="http://localhost/jsunit/testRunner.html?testPage=localhost/foo/alltest.html&amp;autoRun=true&amp;submitResults=localhost:10000/jsunit/acceptor"
    description="url is the URL (HTTP or file protocol) to open in the browser. For a JsUnit Server, this is a mandatory property for a test run if the server is not passed the 'url' parameter. For example: 'http://myhost.mycompany.com:8080/jsunit/testRunner.html?testPage=http://myhost.mycompany.com:8080/jsunit/tests/jsUnitTestSuite.html'"
    />


bin,lib,loggingPropertiesFileをそれぞれ修正する。

<property name="bin" location="${jsunitHome}/java/bin"/>
<property name="lib" location="${jsunitHome}/java/lib"/>
<property name="loggingPropertiesFile" location="${jsunitHome}/logging.properties"/>

Antで実行してみる。


build.xmlを設置したプロジェクトディレクトリでantコマンドを実行する。

$ ant


ブラウザが起動してテストが実行されて、自動的に閉じられる。
logsディレクトリに結果ログが出力される。

Redmineを入れてみる。

Redmineってどうなんだろうと思ってたので入れてみる。
インストール先のOSはVine Linux 5.2。
ApacheとかMySQLはすでに入ってる。


メールとかログとかの設定は飛ばして、
ひとまずの導入とApacheで動かすとこまでやってみる。

入手と事前準備

RubyForgeから最新のアーカイブをダウンロードする。
現時点(2010年12月10日)のバージョンは1.0.4。


RubyRubyGems関係入れてなかったのでaptで入れる。
入れたパッケージは次の通り。
irb (バージョン 1.8.7.174-3vl5)
rdoc (バージョン 1.8.7.174-3vl5)
ruby (バージョン 1.8.7.174-3vl5)
ruby-devel (バージョン 1.8.7.174-3vl5)
rubygems (バージョン 1.3.7-1vl5)
ruby-openssl (バージョン 1.8.7.174-3vl5)
これでgemコマンドが使える。


Rackを入れる。指定バージョンじゃないとだめみたい。
# gem install rack -v=1.0.1
Rakeも入れる。
# gem install rake
入れただけだとパスが通っていないので、PATHに/var/lib/gems/1.8/binを追加する。
MySQLのドライバー入れる。
# gem install mysql
No definition for <ナニガシ>
がいっぱい出たけど、
Successfully installed mysql-2.8.1
って書いてあるから成功っぽい。

インストール


インストール手順見ながらインストール。


まず、アーカイブ展開して/var/vhosts/redmineにリネームして設置する。
$ tar zxvf redmine-1.0.4.tar.gz
$ mv redmine-1.0.4 /var/vhosts/redmine


MySQLredmineユーザとデータベース作って、権限を与える。
すでにphpMyAdminいれてたので、さくっと作る。
Redmineホームディレクトリに移動する。
$ cd /var/vhosts/redmine


config/database.yml.exampleを元にdatabase.ymlを作る。
$ cp config/database.yml.example config/database.yml
productionの項目を編集して保存する。
$ vi config/database.yml

production:
  adapter: mysql
  database: redmine
  host: localhost
  username: redmine
  password: <パスワード>
  encoding: utf8

で、インストール手順4には

4. セッションストア秘密鍵を生成してください。
この手順はRedmine r2493以降の trunk またはRedmine 0.8.7以降を利用する場合に必要です。
rake config/initializers/session_store.rb

って書いてあるけど、そんなファイル見つからない。


調べたらコマンド実行して作るみたいなので、実行してみる。
$ rake generate_session_store
できたみたいなので中身確認。
$ cat config/initializers/session_store.rb
なんかできてる。


データベースにテーブルを作るコマンドを実行する。
$ rake db:migrate RAILS_ENV="production"


デフォルトデータの登録コマンドを実行する。
$ rake redmine:load_default_data RAILS_ENV="production"
Select language:が出るので、jaを入力してEnter。
Default configuration data loaded.
と出たので、OKっぽい。


ディレクトリのパーミッションを設定する。
実行ユーザがapacheになるので、apacheで。
$ sudo chown -R apache.apache files log tmp public/plugin_assets
$ sudo chmod -R 755 files log tmp public/plugin_assets


動作確認する。
$ sudo ruby script/server webrick -e production
サーバが起動するので、
http://localhost:3000/にブラウザアクセス。
画面でた。

Apacheで使う設定


今度はこっちを参考に、Apacheで動かすための設定をする。


依存してるライブラリをaptで入れる。
curl-devel (バージョン 7.19.6-2vl5)
libidn-devel (バージョン 1.11-2vl5)


Passengerをインストールする。
# gem install passenger


Apache2用のモジュールを入れるコマンドを実行する。
# passenger-install-apache2-module


Welcome to the Phusion Passenger Apache 2 module installer, v3.0.1.
が出るので、Enter。


The Apache 2 module was successfully installed.
と出て、Apacheの設定しろよ的な内容がでるのでEnter。
VirtualHost設定はこうだよ的なメッセージがでて完了。


/etc/apache2/conf.d内にpassenger.conf作る。
# vi /etc/apache2/conf.d/passenger.conf

LoadModule passenger_module /var/lib/gems/1.8/gems/passenger-3.0.1/ext/apache2/mod_passenger.so
PassengerRoot /var/lib/gems/1.8/gems/passenger-3.0.1
PassengerRuby /usr/bin/ruby

VirtualHostの設定を追加する。
使い回してる設定をいじったので、いらないのも混ざってる。
>||apache|

DocumentRoot /var/vhosts/redmine/public
ServerName redmine

Options FollowSymLinks
AllowOverride None


Options FollowSymLinks ExecCGI -MultiViews +SymLinksIfOwnerMatch
AllowOverride All
Order allow,deny
allow from all


DirectoryIndex index.rb index.html index.htm
ErrorLog /var/vhosts/redmine/log/error_log

LogLevel warn

CustomLog /var/vhosts/redmine/log/access_log combined
ServerSignature On
AddHandler cgi-script .cgi

|

あと、hostsに名前解決できるように127.0.0.1redmineにした。


Apache再起動。
# /etc/init.d/apache2 restart
ブラウザからhttp://redmine/にアクセス。接続を確認して完了。


導入は結構楽だった。

チャット作るよ。10日目

コメント書き込みとログイン周りを実装する。
昔ながらのバージョンはこれでひとまず完了。

コメント書き込みのアクションを作る。

ログイン済みユーザのコメントPOSTを受けとってファイルに書き込む。


まずテストケース。

<?php
class CommentActionTest extends PHPUnit_Framework_TestCase {

    /**
     * アプリケーションディレクトリ
     */
    const APP_DIR_PATH = '/var/project/chat/fw/site';

    public function setUp() {
        // インスタンスを生成
        $this->act = new CommentAction();
        $this->member = new Member(FILE_MEMBER, 300);
        $this->arc = new Archive(FILE_ARCHIVE, 30);
        // アクションを初期化
        $config = array(
            'appDirPath' => self::APP_DIR_PATH,
            'renderer' => new Renderer()
        );
        $this->act->initialize($config);
    }
    
    public function testRun() {
        // 実行前、データが0件であること
        $this->assertEquals(0, count($this->arc->getAll()));
        // postパラメータをセット
        $_POST['comment'] = 'Test';
        // アクション実行
        $this->act->run();
        // セッションがないのでデータに変化は起きないこと
        $this->assertEquals(0, count($this->arc->getAll()));
        $this->assertEquals(0, count($this->member->getAll()));
    }

    public function testRunWithIdentity() {
        // 実行前、データが0件であること
        $this->assertEquals(0, count($this->arc->getAll()));
        $this->assertEquals(0, count($this->member->getAll()));
        // 新規メンバーとしてデータ登録
        $name = 'Test';
        $id = $this->member->add($name);
        $identity = array(
            'id' => $id,
            'name' => $name
        );
        // セッションにセット
        $_SESSION['identity'] = $identity;
        // 登録データを取得して、保持しておく
        $members = $this->member->getAll();
        $user = $members[0];
        // 更新時間をずらすため1秒待機
        sleep(1);
        // postパラメータをセット
        $comment = 'TestTestTest';
        $_POST['comment'] = $comment;
        // アクション実行
        $this->act->run();
        // ログにpostしたコメントが登録されること
        $logs = $this->arc->getAll();
        $this->assertEquals(1, count($logs));
        $this->assertEquals($name, $logs[0]['name']);
        $this->assertEquals($comment, $logs[0]['comment']);
        // ユーザのタイムスタンプが更新されること
        $members = $this->member->getAll();
        $update = $members[0];
        $this->assertEquals(1, count($members));
        $this->assertEquals($user['id'], $update['id']);
        $this->assertEquals($user['name'], $update['name']);
        $this->assertLessThan($update['tstamp'], $user['tstamp']);
    }

    public static function tearDownAfterClass() {
        // データファイルの後始末
        if(file_exists(FILE_ARCHIVE)) {
            unlink(FILE_ARCHIVE);
        }
        if(file_exists(FILE_MEMBER)) {
            unlink(FILE_MEMBER);
        }
    }
}

アクションクラス。

<?php
class CommentAction extends Action {

    public function run() {
        if(isset($_SESSION['identity']) && isset($_POST['comment'])) {
            // ログイン済みで書き込みポストがある場合
            $identity = $_SESSION['identity']; 
            $comment = $_POST['comment'];
            // アーカイブにデータ追加
            $arc = new Archive(FILE_ARCHIVE, 30);
            $arc->add($identity['name'], $comment);
            // 有効期限を更新して、GCも実行。
            $member = new Member(FILE_MEMBER, 300);
            $member->update($identity['id']);
            $member->gc();
        }
        header("HTTP/1.1 301 Moved Permanently");
        header("Location: /?a=log");
    }
}

ファイル書き込みと同時に、ユーザの期限を延長と期限切れユーザデータの削除も行う。
処理後にリダイレクトさせて書き込みログの表示に飛ばす。

ログインアクションを作る。

ログインっていうかチャットに参加的な部分。


テストケース。

<?php
class LoginActionTest extends PHPUnit_Framework_TestCase {
    
    /**
     * アプリケーションディレクトリ
     */    
    const APP_DIR_PATH = '/var/project/chat/fw/site';

    public function setUp() {
        // インスタンスの生成と初期化
        $this->act = new LoginAction();
        $config = array(
            'appDirPath' => self::APP_DIR_PATH,
            'renderer' => new Renderer()
        );
        $this->act->initialize($config);
    }
    
    public function testRun() {
        // postパラメータを作成
        $name = 'Test';
        $_POST['name'] = $name;
        // 実行前、データがからであること
        $member = new Member(FILE_MEMBER, 300);
        $this->assertEquals(0, count($member->getAll()));
        $arc = new Archive(FILE_ARCHIVE, 30);
        $this->assertEquals(0, count($arc->getAll()));
        // アクション実行
        $this->act->run();
        // 実行後、新規メンバーとして追加されること
        $members = $member->getAll();
        $this->assertEquals(1, count($members));
        $identity = $members[0];
        $this->assertEquals($name, $identity['name']);
        // セッションへユーザデータが登録されていること
        $this->assertEquals($_SESSION['identity']['name'], $identity['name']);
        // ログにユーザ追加データが登録されていること
        $logs = $arc->getAll();
        $this->assertEquals(1, count($logs));
        $this->assertEquals($name, $logs[0]['name']);
        $this->assertEquals('Welcome!', $logs[0]['comment']);
    }

    public function testRunWithNoName() {
        // 実行前データがからであること
        $member = new Member(FILE_MEMBER, 300);
        $this->assertEquals(0, count($member->getAll()));
        $arc = new Archive(FILE_ARCHIVE, 30);
        $this->assertEquals(0, count($arc->getAll()));
        // アクション実行
        $this->act->run();
        // アクション実行後、Unknownユーザとしてユーザ追加されていること
        $members = $member->getAll();
        $this->assertEquals(1, count($members));
        $identity = $members[0];
        $this->assertEquals('Unknown', $identity['name']);
        // セッションへユーザデータが登録されていること
        $this->assertEquals($_SESSION['identity']['name'], $identity['name']);
    }

    public function tearDown() {
        // テストデータの後始末
        if(file_exists(FILE_ARCHIVE)) {
            unlink(FILE_ARCHIVE);
        }
        if(file_exists(FILE_MEMBER)) {
            unlink(FILE_MEMBER);
        }
    }
}

アクションクラス。

<?php
class LoginAction extends Action {
    
    public function run() {
        // ユーザ名初期化
        $name = 'Unknown';
        if(isset($_POST['name']) && mb_strlen($_POST['name']) > 0) {
            // nameがPOSTパラメータにあればユーザ名に使う
            $name = $_POST['name'];
        }
        // メンバーに追加
        $member = new Member(FILE_MEMBER, 300);
        $id = $member->add($name);
        // 認証をセッションに保持
        $_SESSION['identity'] = array(
            'id' => $id,
            'name' => $name
        );
        // 書き込みログに入室を追加
        $arc = new Archive(FILE_ARCHIVE, 30);
        $arc->add($name, 'Welcome!');
        // トップへリダイレクト
        header("HTTP/1.1 301 Moved Permanently");
        header("Location: /");
    }
}

ユーザ名がなければ名無しさん扱いにする。
参加をログに追加して、リダイレクトさせる。

ログアウトアクションを作る。

退場的な部分。


テストケース。

<?php
class LogoutActionTest extends PHPUnit_Framework_TestCase {
    
    /**
     * アプリケーションディレクトリ
     */ 
    const APP_DIR_PATH = '/var/project/chat/fw/site';

    public function setUp() {
        // インスタンスの生成と初期化
        $this->act = new LogoutAction();
        $config = array(
            'appDirPath' => self::APP_DIR_PATH,
            'renderer' => new Renderer()
        );
        $this->act->initialize($config);
    }
    
    public function testRun() {
        // テストユーザを登録
        $name = 'Test';
        $member = new Member(FILE_MEMBER, 300);
        $id = $member->add($name);
        $this->assertEquals(1, count($member->getAll()));
        // セッションにテストユーザデータをセット
        $_SESSION['identity'] = array(
            'id' => $id,
            'name' => $name
        );
        // アクション実行
        $this->act->run();
        // ログに退室データが追加されていること
        $arc = new Archive(FILE_ARCHIVE, 30);
        $log = $arc->getAll();
        $this->assertEquals(1, count($log));
        $this->assertEquals('Good Bye!', $log[0]['comment']);
        $this->assertEquals($name, $log[0]['name']);
        // テストユーザデータが削除されていること
        $this->assertEquals(0, count($member->getAll()));
        // セッションからデータが削除されていること
        $this->assertFalse(isset($_SESSION['identity']));
    }

    public static function tearDownAfterClass() {
        // テストデータの後始末
        if(file_exists(FILE_ARCHIVE)) {
            unlink(FILE_ARCHIVE);
        }
        if(file_exists(FILE_MEMBER)) {
            unlink(FILE_MEMBER);
        }
    }
}

アクションクラス。

<?php
class LogoutAction extends Action {
    
    public function run() {
        if(isset($_SESSION['identity'])) {
            // ログイン済みの場合、解除処理
            $identity = $_SESSION['identity'];
            // 書き込みログに退出を記録
            $arc = new Archive(FILE_ARCHIVE, 30);
            $arc->add($identity['name'], 'Good Bye!');
            // メンバーから削除
            $member = new Member(FILE_MEMBER, 300);
            $member->remove($identity['id']);
            // 認証を解除
            unset($_SESSION['identity']);
        }
        // トップへリダイレクト
        header("HTTP/1.1 301 Moved Permanently");
        header("Location: /");
    }
}

退場を書き込みして、ユーザデータとセッション消してリダイレクト。


昔ながらの版はさくっと作ろうと思ったのに、
「10日で…」本の1日相当に随分かかってしまった…。無念。
作りが甘い部分いっぱいあるし。中途半端になっちゃった。
作りながら記録付けるのって大変だ。