gist

ラベル Node の投稿を表示しています。 すべての投稿を表示
ラベル Node の投稿を表示しています。 すべての投稿を表示

2012年3月29日木曜日

MVCフレームワーク「Coke」をインストールしてみた。

Coke はNode用のMVCフレームワークです。

一気にインストール&アプリの雛形生成です。

$ npm install -g coke
$ coke new coke_sample
$ !$
$ npm install -l


ファイル構成はこんな感じです。

$ tree .
.
├── README.md
├── app
│   ├── controllers
│   │   ├── application.js
│   │   └── welcome.js
│   ├── helpers
│   │   └── application.js
│   ├── libs
│   ├── locals
│   │   └── en
│   │       └── welcome.js
│   ├── middlewares
│   │   ├── csrf.js
│   │   ├── err404.js
│   │   ├── err500.js
│   │   └── session.js
│   ├── models
│   └── views
│       ├── common
│       │   └── _nav.html
│       ├── error
│       │   ├── 404.html
│       │   └── 500.html
│       ├── layouts
│       │   └── default.html
│       └── welcome
│           └── index.html
├── config
│   ├── assets.yml
│   ├── dev
│   │   ├── config.yml
│   │   └── express.js
│   ├── prod
│   │   ├── config.yml
│   │   └── express.js
│   ├── routes.js
│   └── test
│       ├── config.yml
│       └── express.js
├── db
│   └── schema.js
├── doc
│   └── README.md
├── log
│   ├── monit.log
│   ├── nginx.log
│   ├── static.log
│   └── upstart.log
├── package.json
├── public
│   ├── apple-touch-icon-114x114-precomposed.png
│   ├── apple-touch-icon-129x129-precomposed.png
│   ├── apple-touch-icon-57x57-precomposed.png
│   ├── apple-touch-icon-72x72-precomposed.png
│   ├── apple-touch-icon-precomposed.png
│   ├── apple-touch-icon.png
│   ├── assets
│   ├── css
│   │   └── common
│   │       ├── base.css
│   │       ├── flash.css
│   │       ├── footer.css
│   │       ├── header.css
│   │       ├── nav.css
│   │       ├── reset.css
│   │       └── util.css
│   ├── favicon.ico
│   ├── img
│   │   └── sprite.png
│   ├── js
│   │   └── common
│   │       └── ga.js
│   └── robots.txt
├── server.js
├── test
└── tmp

i18nやスマートフォン用のサイトにも対応していそうです。設定はymlですね。

サーバの起動は、coke コマンドで。mongodbを起動してから。

$ coke s

画面は http://localhost:4000 で。

scaffoldもありますが、上手く動作しないみたい。。。

$ coke g scaffold posts title content is_public:bool
exists  app/
exists  app/controllers/
exists  app/models/
exists  app/views/
create  app/views/posts/
update  db/schema.js
create  app/models/Post.js
create  app/views/posts/_form.html
create  app/views/posts/edit.html
create  app/views/posts/index.html
create  app/views/posts/new.html
create  app/views/posts/show.html
update  app/views/common/_nav.html
create  public/css/scaffold.css
create  app/controllers/posts.js
update  config/routes.js


$ coke s

最初の画面

編集

詳細

一覧

生成されたコントローラ app/controllers/posts.js を眺めてみます。

var mongoose = require( 'mongoose' );
var Post = mongoose.model( 'Post' );
var Application = require( CONTROLLER_DIR + 'application' );

module.exports = Application.extend({

  init : function ( before, after ){
    after( this.validation, {
      only : [ 'create', 'update' ]
    }); 

    after( this.unique, {
      only : [ 'create', 'update' ]
    }); 

    after( this.record_not_found, {
      except : [ 'new', 'create', 'index' ]
    }); 
  },  

  'new' : function ( req, res, next ){
    res.render( 'posts/new' );
  },  

  create : function ( req, res, next ){
    Post.create_or_update( new Post(), req.body.post,
      function ( err, post ){
        if( err ){
          next( err );
          return;
        }   

        req.flash( 'flash-info', 'Post created' );
        res.redirect( '/posts/' + post._id );
      }); 
  },  

  index : function ( req, res, next ){
    Post.find( function ( err, posts ){
      if( err ){
        next( err );
        return;
      }   

      res.render( 'posts/index', {
        posts : posts
      }); 
    }); 
 },

  show : function ( req, res, next ){
    Post.findById( req.params.id , function ( err, post ){
      if( post ){
        res.render( 'posts/show', {
          post : post
        });

        return;
      }

      req.msg = 'User';
      next( err );
    });
  },

  edit : function ( req, res, next ){
    Post.findById( req.params.id , function ( err, post ){
      if( post ){
        res.render( 'posts/edit', {
          post : post
        });

        return;
      }

      req.msg = 'User';
      next( err );
    });
  },

  update : function ( req, res, next ){
    Post.findById( req.params.id , function ( err, post ){
      if( post ){
        Post.create_or_update( post, req.body.post,
          function ( err, post ){
            if( err ){
              next( err );
              return;
            }

            req.flash( 'flash-info', 'Post updated' );
            res.redirect( '/posts/' + post._id );
          });

        return;
      }
      req.msg = 'User';
      next( err );
    });
  },

  destroy : function ( req, res, next ){
    Post.findById( req.params.id , function ( err, post ){
      if( post ){
        post.remove( function ( err, post ){
          if( err ){
            next( err );
            return;
          }

          req.flash( 'flash-info', 'Post deleted' );
          res.redirect( '/posts' );
        });

        return;
      }

      req.msg = 'User';
      next( err );
    });
  }
});

一覧、生成、編集、削除と一通りキレイに生成されています。

2012年3月25日日曜日

Hogan.jsを使ってみる

Hogan.jsは、Twitterで開発されたJavaScriptテンプレートエンジンです。2.5KBと軽量でシンプルな仕組みとなっています。Matadorで使われています。

「hogan」ではなく「Hogan.js」です。npm install時に注意。地味にハマりました。

$ npm install hogan.js
npm http GET https://registry.npmjs.org/hogan.js
npm http 304 https://registry.npmjs.org/hogan.js
hogan.js@2.0.0 ../node_modules/hogan.js

sample.coffee

hogan = require 'hogan.js'
template = hogan.compile "@{{name}}"

names = ['初音ミク', '鈴音リン', '鈴音レン']

names.map (vocaloid) ->
  template.render({name:vocaloid})

console.log 'VOCALOID: ' + names.join(' ') + '!' 

実行

$ coffee sample.coffee 
VOCALOID: 初音ミク 鈴音リン 鈴音レン!

{{hogehoge}} に template.render 関数で指定したオブジェクトのhogehogeキーの値が入ってきます。

2012年3月24日土曜日

Valentineを使ってみる

Valentineは、型チェックや関数のイテレーション、ウォーターフォールやキュー、並列処理などの機能を提供するJavaScriptライブラリです。ScalaやHaskell、F#のような関数型言語っぽく記述することもできるそうです。

Valentineは、Matadorの開発者 ded 氏が開発し、Matadorでもも使われています。

npmでインストールします。

$ npm install valentine
npm http GET https://registry.npmjs.org/valentine
npm http 304 https://registry.npmjs.org/valentine
valentine@1.5.1 ./node_modules/valentine

まずはサンプルを CoffeeScript で書いてみます。

sample.coffee

v = require "valentine"

a = v(["a", "b", "c"]).map (letter) ->
  letter.toUpperCase()
.join " "

console.log a

文字a,b,cの配列map関数一文字ずつ大文字に変換してスペースで結合する」を繋げて書けます。

実行してみます。

$ coffee sample.coffee 
A B C

Valentineのwaterfallを使ってみます。

waterfall.coffee

v = require "valentine"

v.waterfall ((callback) ->
  callback null, "提案", "却下"
),((a, b, callback) ->
  console.log a
  console.log b
  callback null, "酒"
),((c, callback) ->
  console.log c
  callback null, "二日酔い"
), (err, result) ->
  console.log result

waterwallは上から順番に実行してくれます。次の関数へ引数とコールバックを投げることで次々と実行されます。

$ coffee waterfall.coffee 
提案
却下
酒
二日酔い

キュー実行の queue や並列実行の parallel もあり便利なライブラリです。Matadorでも使われていますので、覚えていて損はないと思います。

2012年1月30日月曜日

CPUを最大限活かそう。Nodeでclusterを使ってみる

Nodeでは、クラスタを簡単に実装できます。こんなに簡単でいいのか。

server.js

var cluster = require('cluster');
var http = require('http');

// CPUの数
var numCPUs = require('os').cpus().length;

if(cluster.isMaster) {
    for(var i=0; i<numCPUs; i++) {
        // マスターだったらforkします
        var worker = cluster.fork();
        console.log('worker forked: pid=' + worker.pid);
    }   

    // worker終了時のイベントハンドラ
    cluster.on('death', function(worker) {
        console.log('worker ' + worker.pid + ' died');
    }); 
}
else {
    // サーバー起動
    http.Server(function(req, res) {
        res.writeHead(200);
        res.end('Hello World\n');
    }).listen(3000);
    console.log('server started: http://localhost:3000/');
}

// node終了時に、forkしたworkerを終了します。
// これがないとworkerが終了しないです。
process.on('SIGINT', function() {
    if(cluster.isMaster) {
        console.log('master killed: pid=' + process.pid);
    }   
    else {
        console.log('worker killed: pid=' + process.pid);
    }   
    process.exit(0);
});

実行結果(CPUが2つの場合)

$ node server.js 
worker forked: pid=18981
worker forked: pid=18982
server started: http://localhost:3000/
server started: http://localhost:3000/
^Cmaster killed: pid=18980
worker killed: pid=18982
$ worker killed: pid=18981

起動後、Control+Cで終了すると、master→worker→workerの順に終了するのがわかります。

CPUを沢山お持ちの方は、負荷テストするとその効果がわかります。

参考:Node.js の Cluster のベンチマークをとってみた

2012年1月29日日曜日

「デバッグで問題を切り分けよう!」Nodeのデバッグ機能を使ってみる。

Nodeには標準でデバッグ機能が搭載されています。

server.js


var http = require('http');

http.createServer(function(rq, rs) {
    rs.writeHead(200, {'Content-Type': 'text/plain'});
    rs.end('Hello Debugger!');
}).listen(3000, 'localhost');

console.log('サーバーが起動しました: http://localhost:3000/');

デバッグモードで起動するには、nodeコマンドにdebugオプションをつけます。
node debug filePath 
$ node debug server.js 
< debugger listening on port 5858
connecting... ok
break in server.js:1
  1 var http = require('http');
  2 
  3 http.createServer(function(rq, rs) {
debug> 

debug> で入力待ちの状態になります。
break in server.js:1  
は、止まっている位置を示しています。server.jsの1行目で処理が中断しています。
  1 var http = require('http');
  2 
  3 http.createServer(function(rq, rs) {
は、中断している位置周辺のコードが表示されます。

この状態のまま、ブラウザから http://localhost:3000/ にアクセスすると、接続できないはずです。

server.js の1行目で処理が中断しているので、6行目のlisten関数が実行されておらず、ウェブサーバが起動していないためです。

処理を再開するには、debug> で「c」と入力します。

debug> c
< サーバーが起動しました: http://localhost:3000/
debug> 

「サーバーが起動しました」の出力があり、「break in server.js:N」の表示もありません。server.jsの最終行まで実行されています。ブラウザからアクセスすると、Hello Debugger! と表示されるはずです。

server.jsを修正し、クライアントからリクエストを受け取ったところで止めてみます。


var http = require('http');

http.createServer(function(rq, rs) {
    debugger;  // 追加。リクエストがきたら止める。
    rs.writeHead(200, {'Content-Type': 'text/plain'});
    rs.end('Hello Debugger!');
}).listen(3000, 'localhost');

console.log('サーバーが起動しました: http://localhost:3000/');

デバッグモードで実行してみます。

$ node debug server.js 
< debugger listening on port 5858
connecting... ok
break in server.js:1
  1 var http = require('http');
  2 
  3 http.createServer(function(rq, rs) {
debug%gt; c
< サーバーが起動しました: http://localhost:3000/
debug< 

「c」でサーバーが起動したら、ブラウザからアクセスしてみます。処理が完了しないはずです。

デバッガが起動しており、4行目で処理が中断しています。

(続き)
< サーバーが起動しました: http://localhost:3000/
break in server.js:4
  2 
  3 http.createServer(function(rq, rs) {
  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
debug> 

インスタンス rq の中身を知るには、repl を使います。

(続き)
debug> repl
Press Ctrl + C to leave debug repl
>

replが起動します。REPL(Read-Eval-Print-Loop)は、読込→評価→表示を繰り返す対話型評価環境のこと。rubyやphythonなんかで使われています。

rqの中身を表示するには、rqと打ってReturnします。

> rq
{ httpVersion: '1.1',
  method: 'GET',
  headers: 
   { 'cache-control': 'max-age=0',
     'accept-encoding': 'gzip, deflate',
     'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, lik... (length: 119)',
     'accept-language': 'ja-jp',
     accept: 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8',
     connection: 'keep-alive',
     host: 'localhost:3000' },
  client: 

  (ズラーリ)

}
>

ポイントを絞ってHTTPヘッダだけ表示してみます。

> rq.headers
{ 'cache-control': 'max-age=0',
  'accept-encoding': 'gzip, deflate',
  'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, lik... (length: 119)',
  'accept-language': 'ja-jp',
  accept: 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8',
  connection: 'keep-alive',
  host: 'localhost:3000' }
> 

いい具合です。

REPLを抜けるにはCtrl+Cです。

中断している行がわからない場合、bt, listコマンドを使います。

debug> list()
  1 var http = require('http');
  2 
  3 http.createServer(function(rq, rs) {
  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
  7 }).listen(3000, 'localhost');
  8 
  9 console.log('サーバーが起動しました: http://localhost:3000/');
debug> bt
#0 server.js:4:5
debug> 

listは、中断しているコードの前後を表示します。表示された行の真ん中が、現在の行です。(例では、1行目が近いので、実際どこかわかりません。)

btは、現在の行、スタックを表示します。(例では4行目で止まっていることがわかります。)

ウオッチリストを追加しています。

watchでウオッチ追加、unwatchでウオッチ削除、watchersでウオッチの一覧を表示します。

rs.finishedを監視に加え、nコマンドで一行進めます。

(続き)
debug> watch('rs.finished')
debug> n
break in server.js:5
Watchers:
  0: rs.finished = false

  3 http.createServer(function(rq, rs) {
  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
  7 }).listen(3000, 'localhost');
debug>

行が1行進んで5行目に、ウオッチリストと値が表示されます。

nを続けてみます。

(続き)
debug> n
break in server.js:6
Watchers:
  0: rs.finished = false

  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
  7 }).listen(3000, 'localhost');
  8 
debug> n
break in server.js:7
Watchers:
  0: rs.finished = true

  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
  7 }).listen(3000, 'localhost');
  8 
  9 console.log('サーバーが起動しました: http://localhost:3000/');
debug>

7行目で、rs.finishedがtrueに変化しました。

ウオッチを削除してみます。

debug> watchers
  0: rs.finished = true
debug> unwatch(0)
debug> watchers
debug> 

現在の行にブレークポイントを追加してみます。sbコマンドを使います。

debug> sb()
  2 
  3 http.createServer(function(rq, rs) {
  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
* 7 }).listen(3000, 'localhost');
  8 
  9 console.log('サーバーが起動しました: http://localhost:3000/');
 10 
 11 
 12 });
debug>

「*」印がついて7行目にブレークポイントが設定されました。

「c」で継続して、再度ブラウザからアクセスしてみます。

debug> c
break in server.js:4
  2 
  3 http.createServer(function(rq, rs) {
  4     debugger;
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
debug> c
break in server.js:7
  5     rs.writeHead(200, {'Content-Type': 'text/plain'});
  6     rs.end('Hello Debugger!');
* 7 }).listen(3000, 'localhost');
  8 
  9 console.log('サーバーが起動しました: http://localhost:3000/');
debug> 

4行目のdebuggerで一度止まります。c で継続するとブレークポイントの7行目まで処理が飛びます。

こんな感じでデバッグできます。より詳しいデバッグコマンドは、公式リファレンスを参考に。

2012年1月28日土曜日

バージョンアップで最高に美しい!NodeのIDE「Nide」を使ってみよう。

Nideは、Nodeのための美しいIDE(統合開発環境)です。Nideはブラウザで動作します。バージョン0.2からはMac用にアプリケーションも提供されています。


Nideは、Node 0.6.7 以降に対応しています。



早速、インストールしてみます。Macの人は、Nideからdmgファイルをダウンロードしてインストールできます。

ここではコマンドでインストールする方法を紹介します。

ターミナルで

$ sudo npm install -g nide

以上でインストール終了です。簡単ですね。

では、使ってみましょう。

適当にexpressで雛形を作ります。

$ express nide_sample
$ cd nide_sample && npm install

Nideを起動してみます。

$ nide init
Nide running at http://localhost:8123

ブラウザが起動します。これがNideの画面です。画面の左側、Project以下にファイル構成が表示されます。npmの追加削除、ドキュメント閲覧が可能です。



app.js。きれいです。



ファイルは自動的に保存されます。

エディタの上にある「Versions」ボタンを押すと、ファイルのバージョンを表示してくれます。美しい。ブラウザで動いてるのを忘れそうです。


おお。

右側でバージョンを選択して「Revert」ボタンで戻せます。

npmもここから追加、削除が可能です。



一覧の下にある「+」ボタンで追加です。

ダイアログが表示されるので、パッケージ名を入力してインストール完了。


ドキュメントも見れます。便利。

ファイル一覧のリフレッシュ機能、ファイルの表示/非表示機能。


ファイルの検索機能。ニョロってします。

とにかく美しいです。IDEの設計思想が伝わってきます。

これにコード補完機能がついたら完璧!


2012年1月24日火曜日

socket.ioでマウスの動きを共有してみよう

自分のマウスの動きが他の人のブラウザにリアルタイムで表示されたら面白いことできそうです。socket.ioを使って、ブラウザから取得したマウスの位置情報を他のブラウザにブロードキャストしてみます。

$ express mouse_share
$ cd mouse_share && npm install
jade@0.20.0 ./node_modules/jade 
├── mkdirp@0.3.0
└── commander@0.2.1
express@2.5.5 ./node_modules/express 
├── qs@0.4.0
├── mkdirp@0.0.7
├── mime@1.2.4
└── connect@1.8.5
$ npm install socket.io
socket.io@0.8.7 ./node_modules/socket.io 
├── policyfile@0.0.4
├── redis@0.6.7
└── socket.io-client@0.8.7

app.js

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')

var app = module.exports = express.createServer();

var io = require('socket.io').listen(app);
io.set('log level', 1);

// Configuration

app.configure(function(){
  app.set('views', __dirname   '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(require('stylus').middleware({ src: __dirname   '/public' }));
  app.use(app.router);
  app.use(express.static(__dirname   '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});
    
// Routes
    
app.get('/', routes.index);
    
app.listen(3000);
            
io.sockets.on('connection', function(socket) {
    socket.on('mousemove', function(mouse) {
        socket.broadcast.json.send({x:mouse.x, y:mouse.y});
    });             
});                 
                    
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

views/layout.jade

!!!
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js' ,type='text/javascript')
    script(src='/socket.io/socket.io.js' ,type='text/javascript')
    script(type='text/javascript')
        $(document).ready(function() {
            var socket = io.connect('http://localhost:3000/');
            socket.on('message', function(mouse) {
                $('#point').css({
                    'position':'fixed',
                    'width':'8px',
                    'height':'8px',
                    'background-color':'#f00',
                    'border-radius':'8px',
                    'left':mouse.x,
                    'top':mouse.y,
                });
            });
            $(document).mousemove(function(event) {
                socket.emit('mousemove', {x:event.pageX, y:event.pageY});
            });
        });
  body!= body


実行ー。

$ node app.js

実行結果

ブラウザをSafariとChromeで別々のセッションになるように開いて並べます(Chromeのシークレットウィンドウと普通のウィンドウを並べても可)。片方のブラウザ上でマウスを動かすと、もう片方のブラウザの赤丸ポインタが動きます。

カクカクです。

カクカクを減らすには、サーバからの再送をやめてみます。sendの前にvolatileをつけます。

io.sockets.on('connection', function(socket) {
    socket.on('mousemove', function(mouse) {
        socket.broadcast.json.volatile.send({x:mouse.x, y:mouse.y});
    });             
});                 

さっきよりもスムーズに動きます。

超楽しい!

2012年1月18日水曜日

socket.ioでAKB48のツイートを眺めるアプリをつくる

socket.ioはサーバーとクライアント間でソケット通信できるパッケージです。個人的にはHTML5、Nodeで最もアツい技術かと思います。

このsocket.ioとTwitter Streaming APIを組み合わせてAKB48さんのワードが入ったツイートを眺めるアプリを作ってみます。

システム構成
  • Node 0.6.7
  • socket.io 0.8.7
  • express 2.5.5
  • Twitter-Node(SSL対応版)
HTMLテンプレートはjadeです。

Expressをインストールします。

$ sudo npm install express -g
$

Expressで雛形作成を作成します。

$ express tweets_akb48
$ cd tweets_akb48 && npm install
$

socket.ioをインストールします。

$ npm install socket.io
socket.io@0.8.7 ./node_modules/socket.io
├── policyfile@0.0.4
├── redis@0.6.7
└── socket.io-client@0.8.7
$

Twitter-Nodeの最新版をgithubから取得します。

$ cd node_modules
$ git clone git://github.com/technoweenie/twitter-node.git
$

以上で環境が整いました。

app.jsを編集します。ツイッターのアカウントとパスワードを用意しましょう。


/**
 * Module dependencies.
 */   
var express = require('express')
  , app = express.createServer()
  , routes = require('./routes')
  , io = require('socket.io').listen(app)
  , util = require('util')
  , TwitterNode = require('twitter-node').TwitterNode
  ;           
                  

// Configuration   

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(require('stylus').middleware({ src: __dirname + '/public' }));
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});


app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);

app.listen(3000);

io.sockets.on('connection', function(socket) {
    var twitter = new TwitterNode({
        port: 443,
        user: 'ツイッターのアカウント',
        password: 'パスワード',
        track: ['AKB']
    });

    twitter.addListener('tweet', function(tweet) {
        socket.emit('message',tweet.text);
    }).stream();
});

console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);



views/index.jadeを編集します。

#tweets


全部消して一行だけにします。

views/layout.jadeを編集します。


!!! 5
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js')
    script(type='text/javascript', src='/socket.io/socket.io.js')
    script(type='text/javascript')
        $(document).ready(function(){
            var socket = io.connect();
            socket.on('connect', function(){
                socket.on('message', function(data){
                    $('<div/>')
                    .css({
                         'margin':'4px'
                        ,'padding':'4px'
                        ,'background-color':'#eee'
                        ,'border-radius':'4px'
                    })
                    .text(data)
                    .prependTo($('#tweets'))
                    // 古い方から消す
                    if($('#tweets').children().length>100){
                        console.log('remove');
                        $('#tweets div:last').remove();
                    } 
                });
            });
        });
  body!= body

実行してみましょう。

$ node app.js
   info  - socket.io started
The "sys" module is now called "util". It should have a similar interface.
Express server listening on port 3000 in development mode


ブラウザからhttp://localhost:3000/ にアクセスすると、、、

   debug - served static content /socket.io.js
   debug - client authorized
   info  - handshake authorized 15161463341759372366
   debug - setting request GET /socket.io/1/websocket/15161463341759372366
   debug - set heartbeat interval for client 15161463341759372366
   debug - client authorized for
   debug - websocket writing 1::
(この辺にツイートがたくさん)

コンソールがすごい勢いで流れます。

ブラウザもすごい勢いで流れます。

眺めます。
ほう、春の選抜でカチューシャなのか。おい。


2012年1月16日月曜日

npm ERR! Error: EACCES, permission denied '/usr/local/bin/npm'

npmはNodeのパッケージ管理システムです。Nodeをインストールしたらすぐに入れましょう。

Macにインストールするには、以下のようなコマンドを実行します。OSはMac OS X(Lion)です。

$ curl http://npmjs.org/install.sh | sudo sh
...
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
npm@1.1.0 /usr/local/lib/node_modules/npm 
It worked

$ npm -v
1.1.0

npmのウェブサイトにあるコマンドですと、shに対してsudoしておりませんので以下のようなエラーが出ます。

$ curl http://npmjs.org/install.sh | sh
...
npm ERR! Error: EACCES, permission denied '/usr/local/bin/npm'
npm ERR! 
npm ERR! Please try running this command again as root/Administrator.
npm ERR! 
npm ERR! System Darwin 11.2.0
npm ERR! command "/usr/local/bin/node" "/private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package/cli.js" "rm" "npm" "-gf"
npm ERR! cwd /private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package
npm ERR! node -v v0.6.7
npm ERR! npm -v 1.1.0
npm ERR! path /usr/local/bin/npm
npm ERR! code EACCES
npm ERR! message EACCES, permission denied '/usr/local/bin/npm'
npm ERR! errno {}
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package/npm-debug.log
npm not ok
^[[A^Anpm ERR! error installing npm@1.1.0
npm ERR! error rolling back npm@1.1.0 Error: EACCES, permission denied '/usr/local/bin/npm'

npm ERR! Error: EACCES, permission denied '/usr/local/bin/npm'
npm ERR! 
npm ERR! Please try running this command again as root/Administrator.
npm ERR! 
npm ERR! System Darwin 11.2.0
npm ERR! command "/usr/local/bin/node" "/private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package/cli.js" "install" "-gf"
npm ERR! cwd /private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package
npm ERR! node -v v0.6.7
npm ERR! npm -v 1.1.0
npm ERR! path /usr/local/bin/npm
npm ERR! code EACCES
npm ERR! message EACCES, permission denied '/usr/local/bin/npm'
npm ERR! errno {}
^[[Bnpm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /private/var/folders/34/004k_d5175sgk4nmnknsshwm0000gn/T/npm.12523/package/npm-debug.log
npm not ok
It failed

地味にハマります。

2012年1月15日日曜日

Expressの既存アプリをStylusに移行してみる


Stylusはスタイルシートのテンプレートエンジンです。変数や繰り返し、インポートなどをスタイルシートに組み込むことができます。便利!
Nodeでは、Expressでひな形を生成するとき、オプションを指定すると一緒についてきます。
$ express stylus_example -c stylus
$ cd stylus_example && npm install
Stylusのファイルは、public/stylesheet/style.styl にあります。
body  padding: 50px
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serifa
  color: #00B7FF
Nodeを起動してStylusの出力結果を確認します。
$ node app.js
Express server listening on port 3000 in development mode

http://localhost:3000/stylesheets/style.css にアクセスしてスタイルシートを確認します。
出力結果
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
  color: #00b7ff;
}

Node起動時にStylusがstyle.stylファイルを解析し、style.cssに出力するという仕組みです。

既存アプリをStylusへ移行する

すでにExpressで作成済みのウェブアプリケーションでStylusを使えるようにするには、以下のように設定することで使用できます。
$ npm install stylus
$ vim app.js
app.configure(function(){
    app.set('views', __dirname + '/views');
    app.set('view engine', 'jade');
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    // 以下の一行を追加
    app.use(require('stylus').middleware({ src: __dirname + '/public' }));
    app.use(app.router);
    app.use(express.static(__dirname + '/public')); 
});
$ vim public/stylesheets/styles.styl
body
  padding: 50px
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif
a
  color: #00B7FF

連絡帳アプリをStylusに移行する

前回のエントリーで作成した連絡帳アプリにStylusを適用してみます。
Stylusをグローバルインストールします。
$ npm install -g stylus
/usr/local/bin/stylus -> /usr/local/lib/node_modules/stylus/bin/stylus
stylus@0.22.4 /usr/local/lib/node_modules/stylus
├── growl@1.1.0
├── mkdirp@0.0.7
└── cssom@0.2.1
ローカルにもインストールします。
$ npm install stylus
stylus@0.22.4 ./node_modules/stylus 
├── growl@1.1.0
├── mkdirp@0.0.7
└── cssom@0.2.1
app.jsを修正します。
app.configure(function(){
    ...
    app.use(require('stylus').middleware({ src: __dirname + '/public' }));
    ...
});
既存のスタイルシートをStylusに変換します。
$ stylus --css public/stylesheets/style.css public/stylesheets/style.styl
$ vim public/stylesheets/style.styl

style.styl
body
  padding: 50px
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serifa
  color: #00B7FF
既存のCSSファイルを削除します。
$ rm public/stylesheets/style.css

アプリを起動して動作を確認します。
これで移行が完了です。Node起動後、public/stylesheets/style.css ファイルが出力されていると思います。
スタイルシートが複数ある場合、すべてのCSSファイルに対して stylus --css hoge.css hoge.styl を指定します。

スタイルを修正してみます。

style.styl

...
table
    border-collapse collapse
    width 100%
td
    border 1px solid #999
    padding 2px
こんな感じで簡単にかけます。
style.cssの出力はこんな感じです。
...
table {
  border-collapse: collapse;
  width: 100%;
}
td {
  border: 1px solid #999;
  padding: 2px;
}
かなり綺麗に出力されています。

2012年1月14日土曜日

NodeでMongoDBを使って連絡帳アプリを作る


MongoDBはドキュメント指向のNoSQLデータベースです。Express、MongoDBを組み合わせて連絡帳ウェブアプリケーションを構築してみます。

システム構成

  • Mac OS X Lion
  • Node 0.6.7
  • MongoDB 2.0.2
  • フレームワーク: Express
  • HTMLテンプレート: jade

アクション一覧

アクション名URLメソッド説明
トップページ/GET一覧画面にリダイレクトします。
一覧画面表示/addressbookGET連絡先の一覧を表示します。
連絡先を選択すると詳細を表示します。
詳細画面表示/addressbook/show/:idGET連絡先の詳細を表示します。
編集画面表示/addressbook/edit/:idGET連絡先の編集フォームを表示します。
更新/addressbook/update/:idPOST連絡先を更新し、詳細に戻ります。
作成画面表示/addressbook/addGET連絡先の作成フォームを表示します。
作成/addressbook/insertPOST新しい連絡先を挿入し、
連絡先一覧に戻ります。
削除/addressbook/delete/:idGET連絡先を削除し、一覧に戻ります。

目次

  1. MongoDBのインストール
  2. Expressのインストールと雛形作成
  3. モデルの作成
  4. コントローラの作成
  5. ビューの作成

MongoDBをダウンロードしてインストールします。

$ curl -O http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-2.0.2.tgz .
$ rm mongodb-osx-x86_64-1.6.5.tgz 
$ tar -xzvf mongodb-osx-x86_64-2.0.2.tgz 
x mongodb-osx-x86_64-2.0.2/
x mongodb-osx-x86_64-2.0.2/bin/
x mongodb-osx-x86_64-2.0.2/bin/bsondump
x mongodb-osx-x86_64-2.0.2/bin/mongo
x mongodb-osx-x86_64-2.0.2/bin/mongod
x mongodb-osx-x86_64-2.0.2/bin/mongodump
x mongodb-osx-x86_64-2.0.2/bin/mongoexport
x mongodb-osx-x86_64-2.0.2/bin/mongofiles
x mongodb-osx-x86_64-2.0.2/bin/mongoimport
x mongodb-osx-x86_64-2.0.2/bin/mongorestore
x mongodb-osx-x86_64-2.0.2/bin/mongos
x mongodb-osx-x86_64-2.0.2/bin/mongosniff
x mongodb-osx-x86_64-2.0.2/bin/mongostat
x mongodb-osx-x86_64-2.0.2/bin/mongotop
x mongodb-osx-x86_64-2.0.2/GNU-AGPL-3.0
x mongodb-osx-x86_64-2.0.2/README
x mongodb-osx-x86_64-2.0.2/THIRD-PARTY-NOTICES
$ sudo mv mongodb-osx-x86_64-2.0.2/bin/* /usr/local/bin/
Password:
$ rm -fr mongodb-osx-x86_64-2.0.2 mongodb-osx-x86_64-2.0.2.tgz 

データ保存用のディレクトリを作成します。

$ mkdir ~/Library/MongoDB_Data

起動して動作を確認します。(--dbpathは絶対パスです。)

$ mongod --dbpath=/Users/inouetomoyuki/Library/MongoDB_Data/ &
[1] 86938
$ Sat Jan 14 16:21:40 [initandlisten] MongoDB starting : pid=86938 port=27017 dbpath=/Users/inouetomoyuki/Library/MongoDB_Data/ 64-bit host=imonshin.globals.jp
Sat Jan 14 16:21:40 [initandlisten] db version v2.0.2, pdfile version 4.5
Sat Jan 14 16:21:40 [initandlisten] git version: 514b122d308928517f5841888ceaa4246a7f18e3
Sat Jan 14 16:21:40 [initandlisten] build info: Darwin erh2.10gen.cc 9.6.0 Darwin Kernel Version 9.6.0: Mon Nov 24 17:37:00 PST 2008; root:xnu-1228.9.59~1/RELEASE_I386 i386 BOOST_LIB_VERSION=1_40
Sat Jan 14 16:21:40 [initandlisten] options: { dbpath: "/Users/inouetomoyuki/Library/MongoDB_Data/" }
Sat Jan 14 16:21:40 [initandlisten] journal dir=/Users/inouetomoyuki/Library/MongoDB_Data/journal
Sat Jan 14 16:21:40 [initandlisten] recover begin
Sat Jan 14 16:21:40 [initandlisten] info no lsn file in journal/ directory
Sat Jan 14 16:21:40 [initandlisten] recover lsn: 0
Sat Jan 14 16:21:40 [initandlisten] recover /Users/inouetomoyuki/Library/MongoDB_Data/journal/j._0
Sat Jan 14 16:21:40 [initandlisten] recover cleaning up
Sat Jan 14 16:21:40 [initandlisten] removeJournalFiles
Sat Jan 14 16:21:40 [initandlisten] recover done
Sat Jan 14 16:21:40 [websvr] admin web console waiting for connections on port 28017
Sat Jan 14 16:21:40 [initandlisten] waiting for connections on port 27017

別のターミナルを開いて、MongoDBクライアントを起動してみます。

$ mongo
MongoDB shell version: 2.0.2
connecting to: test
Sat Jan 14 16:23:08 [initandlisten] connection accepted from 127.0.0.1:49329 #1
>

MongoDBのコンソールに入りました。起動しているようです。show dbsでデータベースを一覧します。

> show dbs
local (empty)

データベースはまだ存在しません。以下のようなコマンドを打ってみます。

> db.addressbook.save({'name':'Miku Hatsune'});
> db.addressbook.find();
{ "_id" : ObjectId("4f1130033a59d9bef8402fc7"), "name" : "Miku Hatsune" }
> 

コレクションaddressbookにデータが登録されています。

> show dbs;
local (empty)
test 0.203125GB
> 

testというデータベースができています。

exitコマンドでターミナルに戻ります。mongodは起動したままにして、アプリの開発に進みます。(mongodを終了するにはkill -9コマンドを使用します)


Expressとmongooseのインストール

$ sudo npm install -g express
$ express addressbook
$ cd addressbook && npm install
$ npm install mongoose
$ mkdir models
$


モデルContactの作成

models/contact.jsというファイル名でContact(連絡先)のモデルを作成します。

models/contact.js

var mongo = require('mongoose');
mongo.connect('mongodb://localhost/addressbook');

var Schema = mongo.Schema;

var Contact = mongo.model('contacts', new Schema({
    name: String,
    address: String,
    createAt: {type: Date, default: Date.now}
    })  
);

module.exports = Contact;

app.jsにコードを追加してmodels/contactを使えるようにします。

app.js

var express = require('express')
  , routes = require('./routes')
  , contact = require('./models/contact');


コントローラの作成

app.jsに記述していきます。アクション毎に書いていくのが良いのですが、ここでは一気に掲載します。

app.js

// ホーム
app.get('/', function(req, res) {
   res.redirect('/addressbook/list'); 
});

// 一覧表示
app.get('/addressbook/list', function(req, res) {
    contact.find({}, function(err, contacts) {
        if(err) throw err;
        res.render('list', {title:'Address Book', contacts:contacts});
    }); 
});

// 詳細表示
app.get('/addressbook/show/:id', function(req, res) {
    contact.findOne({_id:req.param('id')}, function(err, contact) {
        if(err) throw err;
        res.render('show', {title:'Contact', contact:contact});
    }); 
});

// 編集表示
app.get('/addressbook/edit/:id', function(req, res) {
    contact.findOne({_id:req.param('id')}, function(err, contact) {
        if(err) throw err;
        res.render('edit', {title:'Contact(Edit)', contact:contact});
    }); 
});

// 新規作成表示
app.get('/addressbook/new', function(req, res) {
    res.render('new', {title:'Contact(New)'});
});

// 更新アクション
app.post('/addressbook/update/:id', function(req, res) {
    contact.findById(req.param('id'), function(err, contact) {
        if(!contact)
            throw err;
        else {
            contact.name = req.param('name');
            contact.address = req.param('address');
            contact.save(function(err) {
                if(err)
                    throw err;
                else
                    res.redirect('/addressbook/show/' contact._id);
            });
        }
    });
});

// 追加アクション
app.post('/addressbook/insert', function(req, res) {
    var con = new contact();
    con.name = req.param('name');
    con.address = req.param('address');
    con.save(function(err) {
        if(err) throw err;
        res.redirect('/addressbook/list');
    });
});

// 削除アクション
app.get('/addressbook/delete/:id', function(req, res) {
    contact.remove({_id:req.param('id')}, function(err) {
        if(err) throw err;
        res.redirect('/addressbook/list');
    });
});



ビューの作成

views/list.jade

table(border='1')
    - for (var i=0; i<contacts.length; i  )
        tr
            td: a(href='/addressbook/show/'   contacts[i]._id)= contacts[i].name
            td= contacts[i].address
p
    a(href='/addressbook/new')
        新規作成

views/show.jade

h2 氏名
p= contact.name
h2 住所
p= contact.address
p
    a(href='/addressbook/edit/'   contact._id)
        編集
p
    a(href='/addressbook/delete/'   contact._id)
        削除

views/edit.jade

form(action='/addressbook/update/'   contact._id, method='post')
    h2 氏名
    input#name(name='name', type='text', value=contact.name)
    h2 住所
    input#address(name='address', type='text', value=contact.address)
    p
        input#submit(type='submit', value='保存')

views/new.jade

form(action='/addressbook/insert', method='post')
    h2 氏名
    input#name(name='name', type='text')
    h2 住所
    input#address(name='address', type='text')
    input#submit(type='submit', value='保存')

動作確認

http://127.0.0.1:3000 にアクセスして動作を確認します。

一覧画面

新規作成画面

一覧画面(追加後)

詳細画面

編集画面

2012年1月12日木曜日

Nodeでexpressを試す

Expressは、Node界隈で人気のWebアプリケーションフレームワークです。早速試してみます。

Expressをインストールします。

$ sudo npm install -g express
Password:
/usr/local/bin/express -> /usr/local/lib/node_modules/express/bin/express
express@2.5.5 /usr/local/lib/node_modules/express 
├── mkdirp@0.0.7
├── mime@1.2.4
├── qs@0.4.0
└── connect@1.8.5

アプリのひな形を生成します。

$ express example

   create : example
   create : example/package.json
   create : example/app.js
   create : example/public
   create : example/routes
   create : example/routes/index.js
   create : example/views
   create : example/views/layout.jade
   create : example/views/index.jade
   create : example/public/javascripts
   create : example/public/images
   create : example/public/stylesheets
   create : example/public/stylesheets/style.css

   dont forget to install dependencies:
   $ cd example && npm install

メッセージに従ってコマンドを実行します。

$ cd example/
$ npm install
express@2.5.5 ./node_modules/express 
├── mkdirp@0.0.7
├── mime@1.2.4
├── qs@0.4.0
└── connect@1.8.5
jade@0.20.0 ./node_modules/jade 
├── mkdirp@0.2.2
└── commander@0.2.1

早速起動してみます。

$ node app.js 
Express server listening on port 3000 in development mode

ブラウザで http://127.0.0.0:3000/ にアクセスしてみます。

Nodeを止めるにはターミナルで Control+C を押します。

生成したひな形の中身を見てみましょう。

  • app.js ... アプリケーションのメインルーチンです。このファイルをnode起動時に指定します。
  • node_modules/ ... Nodeのモジュールが入っています。
  • package.json ... モジュールの依存関係を示すファイルです。
  • public/ ... スタティックにアクセスするファイルが入っています。スタイルシート、JavaScript、Imageなど。
  • routes/ ... ルーティングを示すJavaScriptファイルが入っています。
  • views/ ... Viewに関連するファイルが入っています。ここではExpressのデフォルトのテンプレートJade形式のファイルが入っています。

基本的な流れです。ブラウザから http://127.0.0.1:3000/ アクセスすると、app.js の以下のイベントハンドラが呼ばれます。

app.js

app.get('/', routes.index);

app.get(アクセスされたURL, 起動する関数)となります。この関数は、routesディレクトリのindex.jsに定義されています。

routes/index.js

exports.index = function(req, res){
  res.render('index', { title: 'Express' })
};

reqはリクエスト、resはレスポンスです。res.render でViewをレンダリングして返します。res.render(テンプレート名, テンプレートに与えるオブジェクト) となります。ここでは、indexというテンプレートに対して、プロパティtitleに文字列Expressを設定したオブジェクトを与え、レンダリングします。

renderメソッド内では、viewsディレクトリ内のindex.jade をレンダリングします。index.jadeはJadeテンプレートと呼びます。

h1= title
p Welcome to #{title}

h1= title は <h1>Express</h1> に展開されます。renderメソッドで与えたオブジェクトのtitleプロパティの値が h1 に設定されます。

p Welcome to #{title} は、<p>Welcom to Express</p> に展開されます。#{title} もrenderメソッドで与えたオブジェクトのtitleプロパティの値となります。

ここには書いていないhtmlタグやheadタグ、bodyタグは views/layout.jade に書かれています。index.jade は layout.jadeの body に与えられた部品です。Expressには一部分を全体に統合する機能が備わっています。パーシャル機能とか言います。

これでブラウザに以下のようなHTMLが返ります。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Express
</title>
<link rel="stylesheet" href="/stylesheets/style.css"/>
</head><body>
<h1>Express</h1>
<p>Welcome to Express</p>
</body>
</html>

これでようやくブラウザに表示されます。

作り方としては、どこからでも良いかと思いますが、私はviews内にjadeテンプレ作成→routes/index.jsにルーティングを追加 → app.jsにルーティング設定の順でやることが多いです。

2012年1月11日水曜日

mochaでNodeをユニットテストしてみる

Nodeのユニットテストですが、mochaがイイみたいな情報がMLで流れていたので試しに使ってみます。

mochaをインストールする

mochaでは、アサーションにshould.jsやexpect.js、chal、node標準のassertモジュールなどを選べるそうです。ここでは should.js を使いますので同時にインストールします。

$ mkdir mocha_example
$ cd mocha_example/
$ npm install mocha should
should@0.4.2 /usr/local/lib/node_modules/should 
mocha@0.9.0 /usr/local/lib/node_modules/mocha 
 |- growl@1.4.1
 |- debug@0.1.0
 |- commander@0.5.1
 |- say@0.5.0

testディレクトリを用意して、その中にテストコードを作成します。mochaはtestディレクトリ内のすべてのjsファイルをテストコードとして実行します。

$ mkdir test
$ vim test/user.test.js

user.test.js

var should = require('should');
var user = require('../lib/user.js');

describe('User', function() {
    describe('#createUser', function() {
        it('mikuはnullではありません', function() {
            var miku = new user.createUser('Miku');
            should.exist(miku);
        }); 
    }); 
});

mochaを実行してみます。

$ node_modules/mocha/bin/mocha 

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: Cannot find module '../lib/user.js'
    at Function._resolveFilename (module.js:334:11)
    at Function._load (module.js:279:25)
    at Module.require (module.js:357:17)
    at require (module.js:368:17)
    at Object.<anonymous> (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/test/user.test.js:2:12)
    at Module._compile (module.js:432:26)
    at Object..js (module.js:450:10)
    at Module.load (module.js:351:31)
    at Function._load (module.js:310:12)
    at Module.require (module.js:357:17)
    at require (module.js:368:17)
    at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/bin/_mocha:226:27
    at Array.forEach (native)
    at load (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/bin/_mocha:223:9)
    at Object.<anonymous> (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/bin/_mocha:214:1)
    at Module._compile (module.js:432:26)
    at Object..js (module.js:450:10)
    at Module.load (module.js:351:31)
    at Function._load (module.js:310:12)
    at Array.0 (module.js:470:10)
    at EventEmitter._tickCallback (node.js:192:40)

モジュールがありませんのエラーが表示されます。

モジュールを作成します。ひとまずファイルだけ作成します。

$ mkdir lib
$ touch lib/user.js

mochaを実行します。

$ node_modules/mocha/bin/mocha 

  .

  ✔  1 of 1 tests failed:

  0) Userクラスのテスト userオブジェクトの作成テスト mikuはnullではありません:
     TypeError: undefined is not a function
      at Test.fn (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/test/user.test.js:7:24)
      at Test.run (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runnable.js:153:32)
      at Runner.runTest (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:271:10)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:312:12
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:199:14)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:208:7
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:157:23)
      at Array.0 (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)

mochaが起動し、Userオブジェクトがありません。のエラーに変わっています。

lib/user.jsを編集します。まずオブジェクトを作ります。

lib/user.js

exports.createUser = createUser;
function createUser(name) {

}

mochaを実行します。

$ node_modules/mocha/bin/mocha 

  .

   ✔ 1 tests complete (1ms)


グリーンに変わりました。テストを追加します。名前を取得できるようにしてみます。

user.test.js

var should = require('should');
var user = require('../lib/user.js');

describe('User', function() {
    describe('#createUser', function() {
        it('mikuはnullではありません', function() {
            var miku = new user.createUser('Miku');
            should.exist(miku);
        }); 
        // 追加
        it('miku.nameはMikuになります', function() {
            var miku = new user.createUser('Miku');
            miku.should.have.property('name','Miku');
        }); 
    }); 
});

mocha実行。レッドです。

$ node_modules/mocha/bin/mocha 

  ..

  ✔  1 of 2 tests failed:

  0) User #createUser miku.nameはMikuになります:
     AssertionError: expected {} to have a property 'name'
      at Object.property (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/should/lib/should.js:440:12)
      at Test.fn (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/test/user.test.js:12:30)
      at Test.run (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runnable.js:153:32)
      at Runner.runTest (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:271:10)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:312:12
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:199:14)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:208:7
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:157:23)
      at Array.0 (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)


user.jsを修正します。nameプロパティにMikuを設定して返すようにします。

user.js

exports.createUser = createUser;
function createUser(name) {
    return {name:'Miku'};
}

mocha実行。グリーンです。

$ node_modules/mocha/bin/mocha 

  ..

  ✔  2 tests complete (2ms)

これだとMikuしか返ってきませんので、user.test.jsにテストを追加します。

user.test.js

var should = require('should');
var user = require('../lib/user.js');

describe('User', function() {
    describe('#createUser', function() {
        it('mikuはnullではありません', function() {
            var miku = new user.createUser('Miku');
            should.exist(miku);
        }); 
        it('miku.nameはMikuになります', function() {
            var miku = new user.createUser('Miku');
            miku.should.have.property('name','Miku');
        }); 
        // 追加
        it('person.nameは与えられた名前を返します', function() {
            var person = new user.createUser('Ika Musume');
            person.should.have.property('name', 'Ika Musume');
            person = new user.createUser('NIKU');
            person.should.have.property('name', 'NIKU');
        }); 
    }); 
});


mocha実行。

        $ node_modules/mocha/bin/mocha 

  ...

  ✔  1 of 3 tests failed:

  0) User #createUser person.nameは与えられた名前を返します:
     AssertionError: expected { name: 'Miku' } to have a property 'name' of 'Ika Musume', but got 'Miku'
      at Object.property (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/should/lib/should.js:447:12)
      at Test.fn (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/test/user.test.js:16:32)
      at Test.run (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runnable.js:153:32)
      at Runner.runTest (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:271:10)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:312:12
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:199:14)
      at /Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:208:7
      at next (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:157:23)
      at Array.0 (/Users/inouetomoyuki/Dropbox/Projects/node/mocha_example/node_modules/mocha/lib/runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)


user.jsを再び修正。

user.js

exports.createUser = createUser;
function createUser(name) {
    return {name:name};
}

mocha実行。グリーンになります。

$ node_modules/mocha/bin/mocha 

  ...

  ✔ 3 tests complete (30ms)


こんな感じで、テストコード作成→mocha実行(レッド)→user.js修正→mocha実行(グリーン)→リファクタを繰り返していきます。

2012年1月10日火曜日

npm install でエラーになったとき。npm ERR! Could not create

Nodeでは、npmと呼ばれるパッケージ管理システムを使ってパッケージをインストールします。

グローバルインストールするとき、-gオプションをつけますが、/usr/local/lib/node_moduleディレクトリに書き込み権限がない場合、以下のようなエラーが表示されます。

[inoue@air express_test]$ npm install express -g
npm http GET https://registry.npmjs.org/express
npm http 200 https://registry.npmjs.org/express
npm http GET https://registry.npmjs.org/express/-/express-2.5.5.tgz
npm http 200 https://registry.npmjs.org/express/-/express-2.5.5.tgz
npm ERR! Could not create /usr/local/lib/node_modules/___express.npm
npm ERR! error installing express@2.5.5
npm ERR! Error: EACCES, permission denied '/usr/local/lib/node_modules/___express.npm'
npm ERR! Report this *entire* log at:
npm ERR!     <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR!     <npm-@googlegroups.com>
npm ERR! 
npm ERR! System Darwin 11.2.0
npm ERR! command "node" "/usr/local/bin/npm" "install" "express" "-g"
npm ERR! cwd /Users/inoue/Dropbox/Projects/node/mocha_test
npm ERR! node -v v0.6.7
npm ERR! npm -v 1.1.0-beta-10
npm ERR! path /usr/local/lib/node_modules/___express.npm
npm ERR! code EACCES
npm ERR! message EACCES, permission denied '/usr/local/lib/node_modules/___express.npm'
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/inoue/Dropbox/Projects/node/mocha_test/npm-debug.log
npm not ok

この場合、sudoコマンドを使ってインストールします。

[inoue@air mocha_test]$ sudo npm install express -g
npm http GET https://registry.npmjs.org/express
npm http 304 https://registry.npmjs.org/express
npm http GET https://registry.npmjs.org/mime
npm http GET https://registry.npmjs.org/connect
npm http GET https://registry.npmjs.org/qs
npm http GET https://registry.npmjs.org/mkdirp/0.0.7
npm http 200 https://registry.npmjs.org/mkdirp/0.0.7
npm http GET https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz
npm http 200 https://registry.npmjs.org/mime
npm http 200 https://registry.npmjs.org/qs
npm http 200 https://registry.npmjs.org/connect
npm http GET https://registry.npmjs.org/connect/-/connect-1.8.5.tgz
npm http 200 https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz
npm http 200 https://registry.npmjs.org/connect/-/connect-1.8.5.tgz
npm http GET https://registry.npmjs.org/formidable
npm http 200 https://registry.npmjs.org/formidable
/usr/local/bin/express -> /usr/local/lib/node_modules/express/bin/express
express@2.5.5 /usr/local/lib/node_modules/express 
%u251C%u2500%u2500 mkdirp@0.0.7
%u251C%u2500%u2500 qs@0.4.0
%u251C%u2500%u2500 mime@1.2.4
%u2514%u2500%u2500 connect@1.8.5

エラーをよく読めば「npm ERR! Error: EACCES, permission denied」とありますので気づくと思います。