gist

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月28日水曜日

node-hijack でNode.jsをハイジャックしてみる

node-hijackは、NodeのStubライブラリです。名前の通りNodeのライブラリを乗っ取って、後から実行できるというもの。

$ npm install hijack
hijack@0.1.0 ../node_modules/hijack

いつも通り、CoffeeScriptで書いてみます。


hijack = require 'hijack'
util = require 'util'
hijacked = hijack.require 'util'

hijacked.replace 'log', (message) ->
  console.log 'Nodeを乗っ取ったぜ! ' + message

util.log 'Timestamped message.'

hijack.restore()

hijack.require 'util' でハイジャックするモジュールを決め、hijack.replace で関数を乗っ取ります。


$ coffee sample.coffee 
Nodeを乗っ取ったぜ! Timestamped message.

Stubとして使えそうです。名前がいいね。

2012年3月27日火曜日

basic-loggerを使ってみる

basic-loggerはシンプルなログ出力ライブラリです。

$ npm install basic-logger
basic-logger@0.4.0 ./node_modules/basic-logger 
├── pkginfo@0.2.3
└── underscore@1.2.4

logger = require 'basic-logger'

logger.setLevel 'info'

customConfig =
  showMillis: true
  showTimestamp: true

log = new logger customConfig

log.error 'エラーだよ'
log.warn '警告!'
log.info '情報情報'
log.debug 'デバッグはもう嫌だ'
log.trace 'トレース'

実行結果

$ coffee server.coffee 
[2012-03-30 01:48:16] basic-logger (info) Setting log level to 'info'
[2012-03-30 01:48:16.347] (error) エラーだよ
[2012-03-30 01:48:16.348] (warning) 警告!
[2012-03-30 01:48:16.348] (info) 情報情報

customConfigでミリ時間表示とタイムスタンプの表示をONにしています。

この他のオプションとして、printObjFunc(オブジェクトと関数の中を表示)、prefix(出力内容にプレフィックスを指定)があります。

使い方がシンプルです。大体のフレームワークにはついていますが、ちょっとした確認にはいいかも知れません。コールドリーディングの入門としても良いかも知れません。

2012年3月26日月曜日

GeddyJS を使ってみる

GeddyJSはBootstrap付きのMVCフレームワークです。

インストールからアプリ生成まで

$ npm install -g geddy jake
$ geddy app geddy_sample
$ cd geddy_sample

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

$ tree .
.
├── app
│   ├── controllers
│   │   ├── application.js
│   │   └── main.js
│   ├── models
│   └── views
│       ├── layouts
│       │   └── application.html.ejs
│       └── main
│           └── index.html.ejs
├── config
│   ├── development.js
│   ├── environment.js
│   ├── init.js
│   ├── production.js
│   └── router.js
├── lib
├── log
├── node_modules
└── public
    ├── css
    │   ├── bootstrap.css
    │   ├── bootstrap.min.css
    │   ├── bootstrap.responsive.css
    │   ├── bootstrap.responsive.min.css
    │   └── style.css
    ├── favicon.ico
    ├── img
    │   ├── glyphicons-halflings-white.png
    │   ├── glyphicons-halflings.png
    │   └── whitey.png
    └── js
        ├── bootstrap.js
        ├── bootstrap.min.js
        └── jquery.min.js

14 directories, 21 files

ビューレンプレートにejsを使っています。bootstrapも始めから生成されます。

起動は geddy コマンドです。

$ geddy

ブラウザから http://localhost:4000 でアクセスします。

キレイ!

Bootstrapまで自動生成できるMVCフレームワークとしては、よくできているように見えます。

チュートリアルもわかりやすいです。お試しあれ。

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年3月23日金曜日

Klassを使ってみる

Klassは、JavaScriptにもう少し使いやすいクラスの概念を持ち込めるライブラリです。

$ npm install klass

Person.js

var klass = require('klass')
var Person = klass(function (name) {
    this.name = name;
})
  .methods({
    sing:function() {
      console.log(this.name+' is singing');
    }   
  })  

module.exports = Person;

継承も使える

SuperHuman.js

var Person = require('./Person')
var SuperHuman = Person.extend(function (name) {
})
.methods({
  sing: function() {
    this.supr()
    this.fly()
  },  
  fly: function() {
    console.log(this.name + ' は空を飛ぶ')
  }
})

module.exports = SuperHuman;

main.js

var Person = require('./Person');
var SuperHuman = require('./SuperHuman');
var miku = new Person('初音ミク')
miku.sing();
  
var yazawa = new SuperHuman('矢沢永吉');
yazawa.sing();

実行結果。

$ node main.js 
初音ミク is singing
矢沢永吉 is singing
矢沢永吉 は空を飛ぶ

プライベートやスタティックも可能。いろんなフレームワークで使われているので、覚えていて損はないかと。

2012年3月21日水曜日

複数のJavaScriptライブラリをコンパクトに!「Ender」をインストール。

近頃のウェブサイトでは、複数のJavaScriptライブラリが使わるのが珍しく有りません。

複数のJavaScriptライブラリをまとめて管理できるのが「Ender」です。例えば、domready、qwery、underscoreといったライブラリをコンパクトにまとめて ender.js として呼び出して使うことができます。

Enderのインストールには、node と npm が必要です。Nodeの開発環境が整っていれば、問題なくインストールできます。

nodeやnpmインストール方法は

あたりを参考に(OSはMacです)。

では、Enderを早速インストールしてみます。

$ npm install -g ender

まとめたいパッケージを複数指定してみます。ender build package1 package2... のように指定します。

$ ender build domready qwery underscore
Welcome to ENDER - The no-library library
-----------------------------------------
installing packages: "ender-js domready qwery underscore"...
this can take a minute...
npm http GET https://registry.npmjs.org/ender-js
npm http GET https://registry.npmjs.org/domready
npm http GET https://registry.npmjs.org/qwery
npm http GET https://registry.npmjs.org/underscore
npm http 200 https://registry.npmjs.org/underscore
npm http 200 https://registry.npmjs.org/domready
npm http GET https://registry.npmjs.org/underscore/-/underscore-1.3.1.tgz
npm http GET https://registry.npmjs.org/domready/-/domready-0.2.11.tgz
npm http 200 https://registry.npmjs.org/ender-js
npm http GET https://registry.npmjs.org/ender-js/-/ender-js-0.3.7.tgz
npm http 200 https://registry.npmjs.org/qwery
npm http GET https://registry.npmjs.org/qwery/-/qwery-3.3.3.tgz
npm http 200 https://registry.npmjs.org/domready/-/domready-0.2.11.tgz
npm http 200 https://registry.npmjs.org/underscore/-/underscore-1.3.1.tgz
npm http 200 https://registry.npmjs.org/qwery/-/qwery-3.3.3.tgz
npm http 200 https://registry.npmjs.org/ender-js/-/ender-js-0.3.7.tgz
ender-js@0.3.7 ./node_modules/ender-js 
domready@0.2.11 ./node_modules/domready 
underscore@1.3.1 ./node_modules/underscore 
qwery@3.3.3 ./node_modules/qwery
successfully finished installing packages
assembling packages...
ender.js successfully built!
ender.min.js successfully built!


ender.jsとender.min.jsが出来上がります。

.
├── ender.js
├── ender.min.js
└── node_modules/

このender.jsをhtmlから読み込むだけで、domready, qwery, underscode を使えるようになります。

<script type="text/javascript" src="ender.min.js"></script>
<script type="text/javascript">
$('#content a.button')
  .bind('click.button', function (e) {
    $(this).data('clicked', true).unbind()
    e.preventDefault()
  })
  .css({
      opacity: 1
    , color: 'red'
  })
  .fadeOut(250)
$.map(['a', 'b', 'c'], function (letter) {
  return letter.toUpperCase()
})
$.ajax('/data', function (resp) {
  $('#content').html(resp)
})
</script>

現在有効なライブラリを見るには、ender info コマンドです。


$ ender info
Welcome to ENDER - The no-library library
-----------------------------------------
Your current build type is "build"
Your current minified and compressed library size is 7.2 kB

Active packages:
├── domready@0.2.11 - bullet proof DOM ready method
├── qwery@3.3.3 - blazing fast CSS3 query selector engine
└── underscore@1.3.1 - JavaScript's functional programming helper library.
 

パッケージを追加したい場合は、ender add package コマンドです。

$ ender add bean
Welcome to ENDER - The no-library library
-----------------------------------------
installing packages: "bean"...
this can take a minute...
npm http GET https://registry.npmjs.org/bean
npm http 200 https://registry.npmjs.org/bean
npm http GET https://registry.npmjs.org/bean/-/bean-0.4.9.tgz
npm http 200 https://registry.npmjs.org/bean/-/bean-0.4.9.tgz
bean@0.4.9 ./node_modules/bean
successfully finished installing packages
assembling packages...
ender.js successfully built!
ender.min.js successfully built!

$ ender info
Welcome to ENDER - The no-library library
-----------------------------------------
Your current build type is "build"
Your current minified and compressed library size is 10.8 kB

Active packages:
├── domready@0.2.11 - bullet proof DOM ready method
├── qwery@3.3.3 - blazing fast CSS3 query selector engine
├── underscore@1.3.1 - JavaScript's functional programming helper library.
└── bean@0.4.9 - an events api for javascript

削除は ender remove package コマンドで。

EnderでJavaScriptライブラリが足りない!script書き忘れた!ということがなくなりそうです。是非お試しあれ。

2012年3月20日火曜日

軽量で高機能なテキストエディタ「SublimeText2」で CoffeeScript とか。

この間のNode学園4時限目で同じテーブルになったエンジニアさんから、Sublime Text 2を教えて頂き、気になっていたので入れてみました。

感想。「軽っ!」

以下からDownloadできます。



CoffeeScript。フォルダを開くと左側にツリーが表示されます。

package.jsonを表示したところ

IntelliSenseもあります

シンタックスたくさん。


CoffeeScriptのシンタックスは、手動でインストールします。


$ cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages/
$ git clone git://github.com/jashkenas/coffee-script-tmbundle CoffeeScript


vimもいいけど、こんだけ軽いと浮気しそうです。vimのキーバインドもできるのね。(それでもまたvimに戻るのでしょうけど。)

参考:

プログラミングエディタ Sublime Text2 を使ってみよう!
http://d.hatena.ne.jp/mizchi/20111021/1319167480

2012年3月19日月曜日

[Node.js] MatadorでMochaを使ったテストを書いてみる

Matadorでmochaを使ったテストをやってみます。

Matadorは前回のエントリーでインストールします。

[Node.js] Matador+CoffeeScriptの環境を構築する

テストフレームワークにMochaとshouldをインストールします。

$ npm install -g mocha should
$ matador init masamune
$ mkdir test

次にテストを書いてみます。モデルからやってみます。

Masamune.test.coffee

app = require('matador').createApp __dirname + '/../'
should = require 'should'

describe 'MasamuneModel', ->
  describe '生成', ->
    it '政宗モデルを生成できる', (done) ->
      MasamuneModel = app.getModel('Masamune', true)
      model = new MasamuneModel()
      should.exist model
      done()


テストを実行してみましょう。

$ mocha

  .

  ✖ 1 of 1 tests failed:

  1) MasamuneModel 生成 政宗モデルを生成できる:
     Error: Unable to find models/MasamuneModel
      at /Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample/node_modules/matador/src/matador.js:78:25
      at /Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample/node_modules/matador/src/matador.js:83:36
      at HTTPServer.getModel (/Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample/node_modules/matador/src/matador.js:201:12)
      at Context.<anonymous> (/Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample/test/MasamuneModel.test.coffee:12:29)
      at Test.run (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runnable.js:143:15)
      at Runner.runTest (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:272:10)
      at /Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:316:12
      at next (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:199:14)
      at /Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:208:7
      at next (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:157:23)
      at Array.0 (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)

「MasamuneModelがありません」と怒られますので、作ってみます。Matadorのジェネレーターを使って生成してみます。

$ matador model Masamune
generating model Masamune
Successfully created ./app/models/MasamuneModel.js
$ js2coffee app/models/MasamuneModel.js > app/models/MasamuneModel.coffee
$ rm app/models/MasamuneModel.js 

もう一回実行してみます。

$ mocha

  .

  ✔ 1 tests complete (9ms)

テストが通りました。

もう少しだけテストを書いてみましょう。

app = require('matador').createApp __dirname + '/../'
should = require 'should'

describe 'MasamuneModel', ->
  describe '生成', ->
    it '政宗モデルを生成できる', (done) ->
      MasamuneModel = app.getModel('Masamune', true)
      model = new MasamuneModel()
      should.exist model
      done()
  describe '取得', ->
    it '伊達家の歴代を取得する', (done) ->
      MasamuneModel = app.getModel('Masamune', true)
      model = new MasamuneModel()
      model.findAll (err, dateList) ->
        dateList.length.should.eql 1
        dateList.should.eql ['伊達政宗']
        done()

実行してみます。

$ mocha

  ..

  ✖ 1 of 2 tests failed:

  1) MasamuneModel 取得 伊達家の歴代を取得する:
     TypeError: Object #<fn> has no method 'findAll'
      at Context.<anonymous> (/Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample/test/MasamuneModel.test.coffee:23:22)
      at Test.run (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runnable.js:143:15)
      at Runner.runTest (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:272:10)
      at /Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:316:12
      at next (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:199:14)
      at /Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:208:7
      at next (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:157:23)
      at Array.0 (/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/mocha/lib/runner.js:176:5)
      at EventEmitter._tickCallback (node.js:192:40)

findAllメソッドがないと怒られます。プロダクトコードを修正します。

app/models/MasamuneModel.coffee


module.exports = (app, config) ->
  app.getModel("Application", true).extend().methods({
    findAll:(callback) ->
      callback(null, ['伊達政宗'])
  }) 

テストを実行してみます。

$ mocha

  ..

  ✔ 2 tests complete (7ms)


無事通りました。

こんな感じでやっていけば、何とかなりそうです。テストコード内の1行目「app」の取得方法がかっこ悪いですが。

2012年3月18日日曜日

[Node.js] Matador+CoffeeScriptの環境を構築する

「Twitter生まれの軽量なMVCフレームワーク「Matador」を試してみた」で紹介した「Matador」がCoffeeScriptに対応したようです。

早速、環境を構築してみます。

MatadorとCoffeeScriptをグローバルインストールします。

$ npm install -g matador
$ npm install -g coffee-script

Matadorでアプリケーションの雛形を作成します。

$ matador init matador_coffee_sample
installing Matador into matador_coffee_sample
Success!
$ cd matador_coffee_sample
$

次にMatadorをローカルインストールします。

現在(2012/03/18)のところ、npm でインストールされる Matador@1.0.11-beta では CoffeeScript が動作しません。Githubからダウンロードしてインストールします。

$ cd node_modules
$ rm -r matador
$ git clone https://github.com/rcs/matador.git
$ cd matador
$ npm install

最後にアプリケーションの雛形で作成された js ファイルを CoffeeScript に一括で変換します。一括変換は、アプリケーションのルートディレクトリ(server.jsがあるディレクトリと同じ階層)で実行します。

$ cd ../..
$ pwd
/Users/inouetomoyuki/Dropbox/Projects/node/matador_coffee_sample

$ npm install -g js2coffee
$ for f in `find . -type f -name '*.js' ! -path '*/node_modules/*'`; do js2coffee $f > ${f%.*}.coffee && rm $f; done

$ tree app
app
├── config
│   ├── development.coffee
│   ├── production.coffee
│   └── routes.coffee
├── controllers
│   ├── ApplicationController.coffee
│   └── HomeController.coffee
├── models
│   ├── ApplicationModel.coffee
│   └── BaseModel.coffee
├── public
│   └── css
│       ├── directory.css
│       └── main.css
└── views
    ├── 404.html
    ├── admin
    │   ├── index.html
    │   └── partials
    │       └── helloworld.html
    ├── directory.html
    ├── index.html
    ├── layout.html
    └── partials
        └── helloworld.html

for f in ...の一行で一括変換しています。

$ for f in `find . -type f -name '*.js' ! -path '*/node_modules/*'`; do js2coffee $f > ${f%.*}.coffee && rm $f; done

実行して見ます。

$ coffee server.coffee 
matador running on port 3000

無事表示されました。

Expressでは、実装が進むにつれMVCがごちゃごちゃになりがちでした。かといってTower.jsは大きすぎて迷子に。Matadorが今のところ丁度いいフレームワークかと思います。CoffeeScript対応で普及にはずみがつきそうですね。

2012年3月17日土曜日

color.jsを使って少しだけカラフルに

color.jsはコンソールへの出力をちょっとだけカラフルにしてくれるパッケージです。

早速インストールしてみます。

$ npm install colors
npm http GET https://registry.npmjs.org/colors
npm http 304 https://registry.npmjs.org/colors
colors@0.6.0-1 ./node_modules/colors
$

コードです。

var colors = require('colors')
console.log('Hello, World'.bold.green);

出力結果

例えばモジュール別に色分けしたりするのに便利かと思います。

2012年3月16日金曜日

GitHubで見えざるピンクのユニコーンを見た。

github落ちたのでスクリーンショット。見えざるピンクのユニコーンって言うらしいです。


ピンクのド派手なユニコーンがお出迎え。お怒りです。

ページ全体はこんな感じに。
こいつの存在感すごい。見えすぎ。名前を決めたい。


ステータスは赤。何があったの。

オレンジと赤を繰り返しているよう。

github落ちるとキツイですね。

2012年3月15日木曜日

MadadorはCoffeeScriptには未対応のようです

この記事は古くなっています。MatadorはCoffeeScriptをサポートしました。

→「[Node.js] Matador+CoffeeScriptの環境を構築する

終電を逃して徒歩2時間。この時間に更新です。

昨日(一昨日)のエントリーで紹介したフレームワークMadadorですが、CoffeeScriptには対応していないようです。(知っている方いたらぜひ教えて下さい。)

server.jsとroute.jsの変換までは動作を確認。

$ js2coffee server.js > server.coffee
$ js2coffee config/routes.js > config/routes.coffee
$ coffee server.coffee
matador running on port 3000

ApplicationController.jsを変換したところで落ちました。

$ js2coffee app/controllers/ApplicationController.js > app/controllers/ApplicationController.coffee
$ coffee server.coffee 
Error: Unable to find controllers/ApplicationController.coffee
    at /Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/src/matador.js:70:25
    at /Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/src/matador.js:120:11
    at Array.forEach (native)
    at Object.each (/Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/node_modules/valentine/valentine.js:26:20)
    at Function.each (/Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/node_modules/valentine/valentine.js:319:15)
    at /Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/src/matador.js:118:11
    at Array.forEach (native)
    at Object.each (/Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/node_modules/valentine/valentine.js:26:20)
    at Function.each (/Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/node_modules/valentine/valentine.js:319:15)
    at /Users/inouetomoyuki/Dropbox/Projects/node/matador_sample/node_modules/matador/src/matador.js:115:9

んー残念。

2012年3月14日水曜日

Twitter生まれの軽量なMVCフレームワーク「Matador」を試してみた

Matadorは、TwitterのOBが開発したNode.jsのMVCフレームワークです。ExpressにMVCの手法を取り入れたような、シンプルで軽量なフレームワークになっています。

インストールからアプリの雛形作成、起動まで。

$ npm install -g matador
$ npm init sample
$ cd sample
$ npm install
$ node server.js
matador running on port 3000

と、まあ、ここまでは簡単です。

少し構造を見てみます。

依存パッケージ

$ npm ls
/Users/inouetomoyuki/Dropbox/Projects/node/sample
└─┬ matador@1.0.11-beta 
  ├── colors@0.6.0-1 
  ├─┬ express@2.5.8 
  │ ├─┬ connect@1.8.5 
  │ │ └── formidable@1.0.9 
  │ ├── mime@1.2.4 
  │ ├── mkdirp@0.3.0 
  │ └── qs@0.4.2 
  ├── hogan.js@2.0.0 
  ├── klass@1.2.2 
  ├─┬ optimist@0.3.1 
  │ └── wordwrap@0.0.2 
  ├── uglify-js@1.2.5 
  └── valentine@1.5.1 

Express, Hogan, Klassを使っています。

アプリ作成直後のディレクトリ構造

$ tree app
server.js
node_modules/
app
├── config
│   ├── development.js
│   ├── production.js
│   └── routes.js
├── controllers
│   ├── ApplicationController.js
│   └── HomeController.js
├── models
│   ├── ApplicationModel.js
│   └── BaseModel.js
├── public
│   └── css
│       ├── directory.css
│       └── main.css
└── views
    ├── 404.html
    ├── admin
    │   ├── index.html
    │   └── partials
    │       └── helloworld.html
    ├── directory.html
    ├── index.html
    ├── layout.html
    └── partials
        └── helloworld.html

model, view, controller できちんと分かれていますね。シンプルです。

MongoDBを使って、タイトルと本文を登録するアプリを作ってみます。

まずはHelloWorldっぽいのを削除します。

$ app/controllers/HomeController.js
$ app/views/index.html
$ app/views/partials/helloworld.html

MongoDBはググってインストールして起動させておきましょう。

mongoose, mongodbをインストールします。

$ npm install mongoose
$ npm install mongodb

mongoを設定します。

app/models/ApplicationModel.js

module.exports = function (app, config) {
  return app.getModel('Base', true).extend(function() {
    this.mongo = require('mongodb')
    this.mongoose = require('mongoose')
    this.Schema = this.mongoose.Schema
    this.mongoose.connect('mongodb://localhost/madador')
  })  
}

scaffold機能でmodelとcontrollerの雛形を作成します。

$ madator controller Post
$ madator model Post

雛形にアクションを追加していきます。

app/models/PostModel.js

module.exports = function (app, config) {
  
  return app.getModel("Application", true).extend(function() {
      this.DBModel = this.mongoose.model('Post', new this.Schema({
          title: {type: String, require:true, trim:true }
        , body: {type: String, require:true, trim:true }
      }))
  })
  .methods({
    save: function(title, body, callback) {
      var post = new this.DBModel({
          title: title
        , body: body
      })
      post.save(callback)
    }
  , index: function(callback) {
      this.DBModel.find(callback)
    }
  })
}

app/controllers/PostController.js

module.exports = function (app, config) {
  
  return app.getController("Application", true).extend(function() {
    this.viewFolder = 'posts' // まだ動作しない模様
  })      
  .methods({
    index: function (req, res) {
      var PostModel = app.getModel('Post', true)
      var model = new PostModel()
      var _self = this
      model.index(function(err, posts) {
        if(err) throw err;
        _self.render(res, 'index', {
          posts: posts
        })
      })
    }
  , form: function(req, res) {
      this.render(res, 'new', {})
    }
  , create: function(req, res) {
      var PostModel = app.getModel('Post', true)
      var model = new PostModel()
      var _self = this;
      model.save(req.body.title, req.body.body, function(err) {
        if(err) throw err;
        res.redirect('/')
      })
    }
  })
}

ビューを作ります。登録フォームと一覧だけ。

app/views/index.html

<table border="1">
<tr>
<td>タイトル</td>
<td>一言</td>
</tr>     
  
{{#posts}} 
<tr>  
  <td><b>{{title}}</b></td>
<td>{{body}}</td> 
</tr> 
{{/posts}}
</table>
<a href="/posts/new">新規投稿</a>

app/views/new.html

<form action="/posts/create" method="post">
<label>タイトル</label><br />
<input type="input" name="title"><br />
<label>本文</label><br />
<textarea name="body"></textarea><br />
<input type="submit" value="登録">

</form>

ルーティングを定義します。

app/config/routes.js

module.exports = function (app) {
  return { 
    root: [
       ['get', '/', 'Post', 'index']
     ,  ['get', '/posts', 'Post', 'index']
     ,  ['get', '/posts/new', 'Post', 'form']
     ,  ['post', '/posts/create', 'Post', 'create']
    ]
  }
}

起動してみます。

http://localhost:3000/posts/new

http://localhost:3000/

viewsディレクトリ内をposts/new.htmlのように配置するには、PostController のコンストラクタに this.viewFolder = "posts" みたいにするそうですが、まだ動作しないようです。GitHub Issue

Matadorは Tower.jsに比べるてシンプルなので理解しやすいですね。CoffeeScriptに対応しないかな。

追記

MatadorはCoffeeScriptに対応しました。対応方法は、[Node.js] Matador+CoffeeScriptの環境を構築する をご覧下さいませ。

2012年3月13日火曜日

Safari 5.1.4 でも express.csrf は undefined だった

少し前のエントリーで、Node.jsでexpress.csrf を有効にした場合、Safariで上手く動いていないのでは?と書きました。

先日Safari 5.1.4がリリースされましたので、早速アップデート。しかし現象は変わらず。

Tower.jsのcsrfではSafari, Chromeとも undefinedではないものの、CSRFの値は固定値。

config/application.coffee

@use "csrf" のコメントを外して有効化。


    @use "csrf"

リソースを確認。metaタグにcsrf_tokenに値が入るも固定値。


<meta name="csrf-token" content="WyfqwvnxM7x3WuSMhTrMR9oU" />

うーむ。僕の使い方が間違っているのかしら。。。

2012年3月12日月曜日

Tower.jsのドキュメントはgithubのwikiを見るべし。

Tower.jsのドキュメントは、towerjs.org を見ていると意図する通りに動かないことが多々あります。Tower.jsのドキュメントは githubのwikiを見たほうがいいかと。結構なボリュームです。ただ、これも誤字脱字だらけでコードをそのまま写経しても動かなかったり。

まだまだ発展途上で日々改善されています。期待して使ってみようと思います。

公式ガイド はTower.jsの機能や修正に追いついていないことがあり、そのまま動作しないことがある。

GitHubのwiki。機能が反映されるのが早い。

結構なボリューム

2012年3月11日日曜日

nvmでsudo npm install -g から卒業する

MacでNode.jsをpkgでインストールすると、nodeの実行ファイルが /usr/local/bin にインストールされます。

npm install でインストールしたパッケージは、/usr/local/lib/node_modules に配置されます。

グローバルインストール(npm install -g)すると、パーミッションエラーでインストールできないことがあります。

npm ERR! message EACCES, permission denied

回避のため、sudo npm install -g hoge みたいに sudo しておりました。後々、面倒です。

nvm をインストールして sudo npm install -g を卒業です。

nvm はNodeのバージョンを切り替えて実行できるシェルプログラムです。

インストール方法

$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm install v0.6.12
$ nvm use v0.6.12
Now using node v0.6.12
$ node -v
v0.6.12
$ which node
/Users/inouetomoyuki/.nvm/v0.6.12/bin/node

nvm install version で指定したバージョンのnodeをインストールして、nvm use version で使用するバージョンを指定できます。

このままですと、ターミナルを起動する度に、nvm.sh を実行する必要があって面倒です。

~/.bash_profile に以下のように記述しておくと便利です。

~/.bash_profile

source ~/.nvm/nvm.sh
nvm use v0.6.12
npm_dir=${NVM_PATH}_modules
export NODE_PATH=$npm_dir

ポイントは、NODE_PATHです。npm install のインストール先になります。

$ echo $NODE_PATH
/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules

今のバージョンを調べるには、

$ nvm ls
v0.4.12 v0.6.11 v0.6.12
current:  v0.6.11

です。

パッケージの中には、rubyのgemを使ってインストールするものもあります。例えば design.io。

ruby にも、バージョンを切り替えられる rvm というシェルプログラムがあります。nvm はもともと rvm を node用に実装したものです。

こちらもインストールしておきましょう。

$ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
$ rvm install 1.9.3
$ rvm use 1.9.3
$ rvm ls

rvm rubies

=> ruby-1.9.3-p125 [ x86_64 ]

# Default ruby not set. Try 'rvm alias create default <ruby>'.

# => - current
# =* - current && default
#  * - default

$ which ruby
/Users/inouetomoyuki/.rvm/rubies/ruby-1.9.3-p125/bin/ruby

これで大体のパッケージは sudo なしでグローバルインストールできると思います。

2012年3月10日土曜日

Tower.jsのnpm ERR! にはキャッシュを疑え。

Tower.jsに限った話ではないのですが。

npm install でエラーが出て、update や uninstall を繰り返していると、今までインストールできていたパッケージがインストールできなくなったりします。

僕の場合、design.io や tower のインストールでこんなエラーが多発してどうしても治りませんでした。

$ npm install -g design.io
npm http GET https://registry.npmjs.org/design.io
npm http 200 https://registry.npmjs.org/design.io
npm http GET https://registry.npmjs.org/design.io/-/design.io-0.3.0-6.tgz
npm http 200 https://registry.npmjs.org/design.io/-/design.io-0.3.0-6.tgz
npm ERR! error installing design.io@0.3.0-6
npm ERR! error rolling back design.io@0.3.0-6 Error: EPERM, operation not permitted '/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/design.io/node_modules/express/node_modules/___connect.npm/package'

npm ERR! Error: EPERM, operation not permitted '/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/design.io/node_modules/express/node_modules/___connect.npm/package'
npm ERR! 
npm ERR! Please try running this command again as root/Administrator.
npm ERR! 
npm ERR! System Darwin 11.3.0
npm ERR! command "node" "/Users/inouetomoyuki/.nvm/v0.6.12/bin/npm" "install" "-g" "design.io"
npm ERR! cwd /Users/inouetomoyuki
npm ERR! node -v v0.6.12
npm ERR! npm -v 1.1.4
npm ERR! path /Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/design.io/node_modules/express/node_modules/___connect.npm/package
npm ERR! code EPERM
npm ERR! message EPERM, operation not permitted '/Users/inouetomoyuki/.nvm/v0.6.12/lib/node_modules/design.io/node_modules/express/node_modules/___connect.npm/package'
npm ERR! errno {}
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/inouetomoyuki/npm-debug.log
npm not ok

npm install で途中、ERRが起きてしまうと中途半端なキャッシュが残ってしまうことがあるとのこと。キャッシュファイルが原因でインストールできない現象が起きてしまうというわけです。キャッシュファイルは ~/.npm/.npm か ~/.npm/.npm-cache 配下に保存されます。

design.ioやtower.jsの巨大なパッケージ群では、cleanしていないパッケージもあるのかしら、と妄想。たぶん iniとredis。

で、キャッシュファイルを削除するには、以下のオプションを使います。

$ npm cache clean

ERRが出る場合があります。(この時点でもERR無視して npm install したらERRなくインストールできましたが)

cacheを一覧するコマンドは、

$ npm cache ls
~/.npm/cloud9/
~/.npm/cloud9/0.5.1/
~/.npm/cloud9/0.5.1/package/
~/.npm/cloud9/0.5.1/package/support/
~/.npm/cloud9/0.5.1/package/support/socket.io-client/
~/.npm/cloud9/0.5.1/package/support/socket.io-client/lib/
~/.npm/cloud9/0.5.1/package/support/apf/
~/.npm/cloud9/0.5.1/package/support/apf/elements/
~/.npm/cloud9/0.5.1/package/support/apf/elements/video/
~/.npm/tower/
~/.npm/tower/0.3.2/
~/.npm/tower/0.3.2/package/
~/.npm/tower/0.3.2/package/lib/
~/.npm/tower/0.3.2/package/lib/tower/
~/.npm/tower/0.3.2/package/lib/tower/generator/
~/.npm/tower/0.3.2/package/lib/tower/generator/generators/
~/.npm/tower/0.3.2/package/lib/tower/generator/generators/tower/
~/.npm/tower/0.3.2/package/lib/tower/generator/generators/tower/app/
~/.npm/tower/0.3.9-10/
~/.npm/tower/0.3.9-10/package/
~/.npm/tower/0.3.9-10/package/lib/
~/.npm/tower/0.3.9-10/package/lib/tower/

...

です。階層で表示されます。~/.npm/{パッケージ名}/{バージョン番号} 毎に分類されています。npm cache clean直後ですと、キャッシュを潜っていくとディレクトリしか残っていないようです。

思い切って、~/.npm以下を削除してみます。

$ cd ~/.npm
$ ls -la
total 0
drwxr-xr-x   5 inouetomoyuki  inouetomoyuki   170  3 10 22:15 .
drwxr-xr-x  85 inouetomoyuki  inouetomoyuki  2890  3 10 22:15 ..
drwxrwxrwx   3 inouetomoyuki  inouetomoyuki   102  3 10 21:52 cloud9
drwxrwxrwx   3 inouetomoyuki  inouetomoyuki   102  3 10 22:15 npm
drwxrwxrwx   5 inouetomoyuki  inouetomoyuki   170  3 10 22:15 tower
$ rm -fr cloud9/ npm/ tower/
$ npm cache clean

npm cache clean でエラーが出なくなりました。

再度、tower.jsをインストールして起動してみます。

$ npm install design.io -g
$ npm install tower -g
$ tower new sample
$ cd sample
$ sudo npm install
$ mongod --dbpath ~/Library/MongoDB_Db/ & 
$ node server.js
   info  - socket.io started
[Sat, 10 Mar 2012 13:46:47 GMT] INFO Tower development server listening on port 3000

info:  Name: design.io-watcher::device_manager hook::listening                          Type: hook            Data: 5000
info:  Name: design.io-watcher::device_manager hook::started                            Type: hook            Data: 5000



無事インストールと起動ができました。(最後のsudoはdesign.ioが/Library/Ruby/Gems/1.8のgemを使ってインストールしているため)

何故かわからないけど インストールできない。githubのIssueもない。といった場合、キャッシュを疑ってみるといいことあるかもしれません。

2012年3月9日金曜日

個人的にもうちょっと使いこなしたいCoffeeScriptのTipsメモ

値の範囲


age = 14

chu2 = 12 < age < 15;

文字列の埋め込み


age = 32

message = "あなたは #{ age }歳です。"

複数行の文字列でpreっぽく


message = """
          はたらけど
            はたらけど猶わが生活樂にならざり
              ぢつと手を見る
          """

ブロックコメント


###
@title 一握の砂
@auther 石川啄木
###

関数のバインド

アカウントを客とカートで生成しておいて、ショッピングカートをクリックすると、客がカートを購入する、みたいな。.shopping-cart要素のclickイベントにcustomerのpurchase(cart)メソッドをバインド。

Account = (customer, cart) ->
  @customer = customer
  @cart = cart

  $('.shopping_cart').bind 'click', (event) =>
    @customer.purchase @cart

nullかundefinedだったら代入


jinsei ?= ikigai

分解


# 「<」「impossible」「>」に分解
tag = "<impossible>"

[open, contents..., close] = tag.split("")

# 金持ちと貧乏を入れ替える
kanemochi = 10000
binbo = -10000

[bingo, kanemochi] = [kanemochi, binbo]

2012年3月8日木曜日

Tower.jsの全体像を見てみるの巻。

Tower.jsは、Ruby on railsやCakePHPの人には、すぐに理解できるパッケージでありましょう。ドキュメントも途中のようですが、敷居は低そうです。

前回のエントリー「さらばExpressよ!Tower.jsを試すの巻」に続き、今回は Tower.jsの全体像を見て行きたいと思います。

Tower.jsでは膨大なパッケージが使われていますが、実際、重要そうなモジュールはこんな感じかな、と。

Tower.Applicationクラス

アプリケーションの定義クラス。各アプリはTower.Applicationを継承して設定してきます。

tower new hogehogeコマンドで自動的に生成されます。Expressと比較してコードが劇的に減っているのがわかります。そして美しい。

# config/application.coffee
class App extends Tower.Application
  @configure ->
    @use "favicon", Tower.publicPath + "/favicon.ico"
    @use "static",  Tower.publicPath, maxAge: Tower.publicCacheDuration
    @use "profiler" if Tower.env != "production"
    @use "logger"
    @use "query"
    @use "cookieParser", Tower.session.secret
    @use "session", Tower.session.key
    @use "bodyParser"
    @use "csrf"
    @use "methodOverride", "_method"
    @use Tower.Middleware.Agent
    @use Tower.Middleware.Location
    @use Tower.Middleware.Router

module.exports = global.App = App

Tower.Modelクラス

モデルを定義するクラス。各モデルはTower.Modelを継承して設定してきます。モデル同士の関連(belongsTo, hasMany)もココで定義します。ちょっとした変換もここで。

tower generate scaffold <モデル名>で生成できます。

# app/models/post.coffee
class App.Post extends Tower.Model
  @field "title"
  @field "body"
  @field "tags", type: ["String"], default: []
  @field "slug"

  @belongsTo "author", type: "User"

  @hasMany "comments", as: "commentable"

  @before "validate", "slugify"

  slugify: ->;
    @set "slug", @get("title").replace(/^[a-z0-9]+/g, "-").toLowerCase()

Tower.Routeクラス

ルーティング定義。セッションの生成、破棄もココ。

# config/routes.coffee
Tower.Route.draw ->
  @match "/login", "sessions#new", via: "get", as: "login"
  @match "/logout", "sessions#destroy", via: "get", as: "logout"

  @resources "posts", ->
    @resources "comments"

  @namespace "admin", ->
    @resources "users"
    @resources "posts", ->
      @resources "comments"

  @constraints subdomain: /^api$/, ->
    @resources "posts", ->
      @resources "comments"

  @match "(/*path)", to: "application#index", via: "get"

ビュー

描画部分を定義。CoffeeKupで記述。Bootstrap2に対応!

tower generate view <ビュー名> コマンドで生成できます。

# app/views/posts/new.coffee
formFor "post", (f) ->
  f.fieldset (fields) ->
    fields.field "title", as: "string"
    fields.field "body", as: "text"

  f.fieldset (fields) ->
    fields.submit "Submit"

Layout。指定しなければ、このレイアウトでラップされる。partialだけ書けば良い。

# app/views/layouts/application.coffee
doctype 5
html ->
  head ->
    meta charset: "utf-8"

    title t("title")

    meta name: "description", content: t("description")
    meta name: "keywords", content: t("keywords")
    meta name: "robots", content: t("robots")
    meta name: "author", content: t("author")

    csrfMetaTag()

    appleViewportMetaTag width: "device-width", max: 1, scalable: false

    stylesheets "lib", "vendor", "application"

    javascriptTag "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"
    javascripts "vendor", "lib", "application"

  body role: "application", ->
    if hasContentFor "templates"
      yield "templates"

    nav id: "navigation", role: "navigation", ->
      div class: "frame", ->
        partial "shared/navigation"

    header id: "header", role: "banner", ->
      div class: "frame", ->
        partial "shared/header"

    section id: "body", role: "main", ->
      div class: "frame", ->
        yields "body"
        aside id: "sidebar", role: "complementary", ->
          if hasContentFor "sidebar"
            yields "sidebar"

    footer id: "footer", role: "contentinfo", ->
      div class: "frame", ->
        partial "shared/footer"

  if hasContentFor "popups"
    aside id: "popups", ->
      yields "popups"

  if hasContentFor "bottom"
    yields "bottom"

Tower.Controllerクラス

モデルとビューを操作するコントローラクラス。あるアクションに対してモデルとビューをどうするか定義。モデリングにもよるけど、1つのモデルに対して大体1個定義。

tower generate controller コマンドで生成できます。


# app/controllers/postsController.coffee
class App.PostsController extends Tower.Controller
  index: ->
    App.Post.all (error, posts) =>
      @render "index", locals: posts: posts

  new: ->
    @post = new App.Post
    @render "new"

  create: ->
    @post = new App.Post(@params.post)

    super (success, failure) ->
      @success.html -> @render "posts/edit"
      @success.json -> @render text: "success!"
      @failure.html -> @render text: "Error", status: 404
      @failure.json -> @render text: "Error", status: 404

  show: ->
    App.Post.find @params.id, (error, post) =>
      @render "show"

  edit: ->
    App.Post.find @params.id, (error, post) =>
      @render "edit"

  update: ->
    App.Post.find @params.id, (error, post) =>
      post.updateAttributes @params.post, (error) =>
        @redirectTo action: "show"

  destroy: ->
    App.Post.find @params.id, (error, post) =>
      post.destroy (error) =>
        @redirectTo action: "index"

データベースの定義

どのデータベースを使うか。開発用、テスト用、デモ用、本番用など用途に応じて定義可能。

# config/databases.coffee
module.exports =
  mongodb:
    development:
      name: "app-development"
      port: 27017
      host: "127.0.0.1"
    test:
      name: "app-test"
      port: 27017
      host: "127.0.0.1"
    staging:
      name: "app-staging"
      port: 27017
      host: "127.0.0.1"
    production:
      name: "app-production"
      port: 27017
      host: "127.0.0.1"

多言語の定義

日本語の場合、たぶんjp.coffeeで定義。多言語化は始めからやっておくと吉。

# config/locales/en.coffee
module.exports =
  hello: "world"
  forms:
    titles:
      signup: "Signup"
  pages:
    titles:
      home: "Welcome to %{site}"
  posts:
    comments:
      none: "No comments"
      one: "1 comment"
      other: "%{count} comments"
  messages:
    past:
      none: "You never had any messages"
      one: "You had 1 message"
      other: "You had %{count} messages"
    present:
      one: "You have 1 message"
    future:
      one: "You might have 1 message"

こんなもんかな。

全体的にコードが美しいです。ドキュメントはまだまだ足りませんが、Rails-erやCakePHP-erには読まなくとも大体は理解できるようになっているので、学習時間が少なくて済むのがいいところでしょうか。

2012年3月7日水曜日

さらばExpressよ!Tower.jsを試すの巻

一昨日からExpressのCSRF対策に頭を悩ませておりまして。本日は気分転換でExpressに変わるかもしれない Tower.jsを試してみます。

Tower.js。その名前の通り、すべてがそろったパッケージであります。Scaffold、Bootstrap2、Stylus、MongoDb、CoffeeScript, etc,etc,etc....。npmでインストールされるパッケージ数たるや「東京スカイツリー」であり、アーキテクチャの完成度たるや「エッフェル塔」のような美しさなのであります。

見る?というか見上げる?

$ npm ls
npm WARN node-uuid@1.2.0 dependencies field should be hash of <name>:<version-range> pairs
tower_sample@0.0.1 /Users/inouetomoyuki/Dropbox/Projects/node/tower_sample
├── async@0.1.18 
├── chai@0.4.2 
├── coffee-resque@0.1.4 
├── coffee-script@1.2.0 
├─┬ design.io@0.3.0-6 
│ ├── commander@0.5.2 
│ ├─┬ connect@2.0.2 
│ │ ├── debug@0.5.0 
│ │ ├── formidable@1.0.9 
│ │ ├── mime@1.2.4 
│ │ └── qs@0.4.2 
│ ├─┬ express@2.5.8 
│ │ ├─┬ connect@1.8.5 
│ │ │ └── formidable@1.0.9 
│ │ ├── mime@1.2.4 
│ │ ├── mkdirp@0.3.0 
│ │ └── qs@0.4.2 
│ ├── findit@0.1.2 
│ ├─┬ hook.io@0.8.7-1 
│ │ ├── colors@0.6.0-1 
│ │ ├── dnode-protocol@0.1.1 
│ │ ├── eventemitter2@0.4.8 
│ │ ├── jsonify@0.0.0 
│ │ ├── lazy@1.0.8 
│ │ ├── mdns@0.0.5 
│ │ ├── mkdirp@0.2.2 
│ │ ├─┬ nconf@0.3.1 
│ │ │ └── ini@1.0.2 
│ │ ├─┬ npm@1.0.106 
│ │ │ ├── abbrev@1.0.3 
│ │ │ ├── graceful-fs@1.0.1 
│ │ │ ├── ini@1.0.1 
│ │ │ ├─┬ minimatch@0.0.4 
│ │ │ │ └── lru-cache@1.0.4 
│ │ │ ├── node-uuid@1.2.0 
│ │ │ ├── nopt@1.0.10 
│ │ │ ├── proto-list@1.0.0 
│ │ │ ├── request@2.1.1 
│ │ │ ├── rimraf@1.0.8 
│ │ │ ├── semver@1.0.11 
│ │ │ ├── slide@1.1.3 
│ │ │ └── which@1.0.2 
│ │ ├─┬ optimist@0.2.8 
│ │ │ └── wordwrap@0.0.2 
│ │ ├── pkginfo@0.2.3 
│ │ ├─┬ portfinder@0.2.1 
│ │ │ └── mkdirp@0.0.7 
│ │ ├── prompt@0.1.12 
│ │ ├── semver@1.0.13 
│ │ ├─┬ socket.io@0.8.6 
│ │ │ ├── policyfile@0.0.4 
│ │ │ └── redis@0.6.7 
│ │ ├─┬ socket.io-client@0.8.6 
│ │ │ ├── uglify-js@1.0.6 
│ │ │ ├── websocket-client@1.0.0 
│ │ │ └── xmlhttprequest@1.2.2 
│ │ ├── traverse@0.5.2 
│ │ ├── weak@0.1.5 
│ │ └─┬ winston@0.5.10 
│ │   ├── eyes@0.1.7 
│ │   ├─┬ loggly@0.3.11 
│ │   │ ├── request@2.9.153 
│ │   │ └── timespan@2.2.0 
│ │   └── stack-trace@0.0.6 
│ ├── node-uuid@1.3.3 
│ ├─┬ seq@0.3.5 
│ │ ├─┬ chainsaw@0.0.9 
│ │ │ └── traverse@0.3.9 
│ │ └─┬ hashish@0.0.4 
│ │   └── traverse@0.6.0 
│ ├─┬ socket.io@0.9.0 
│ │ ├── policyfile@0.0.4 
│ │ ├── redis@0.6.7 
│ │ └─┬ socket.io-client@0.9.0 
│ │   ├─┬ ws@0.4.0 
│ │   │ ├── commander@0.5.0 
│ │   │ └── options@0.0.2 
│ │   └── xmlhttprequest@1.2.2 
│ └── underscore.logger@0.3.1 
├── design.io-javascripts@0.3.0-1 
├── design.io-stylesheets@0.3.0-2 
├─┬ forever@0.8.5 
│ ├─┬ broadway@0.1.13 
│ │ ├── colors@0.6.0-1 
│ │ ├── eventemitter2@0.4.8 
│ │ └─┬ optimist@0.3.1 
│ │   └── wordwrap@0.0.2 
│ ├─┬ cliff@0.1.7 
│ │ ├── colors@0.6.0-1 
│ │ └── eyes@0.1.7 
│ ├── daemon@0.4.1 
│ ├─┬ flatiron@0.1.14 
│ │ ├── director@1.0.9-1 
│ │ ├─┬ optimist@0.3.1 
│ │ │ └── wordwrap@0.0.2 
│ │ └─┬ prompt@0.1.12 
│ │   └── colors@0.6.0-1 
│ ├── microtime@0.2.0 
│ ├─┬ minimatch@0.0.5 
│ │ └── lru-cache@1.0.5 
│ ├─┬ nconf@0.5.1 
│ │ ├── ini@1.0.2 
│ │ └─┬ optimist@0.3.1 
│ │   └── wordwrap@0.0.2 
│ ├── node-fork@0.4.2 
│ ├─┬ nssocket@0.3.7 
│ │ ├── eventemitter2@0.4.8 
│ │ └── lazy@1.0.8 
│ ├─┬ optimist@0.2.8 
│ │ └── wordwrap@0.0.2 
│ ├── pkginfo@0.2.3 
│ ├─┬ portfinder@0.2.1 
│ │ └── mkdirp@0.0.7 
│ ├─┬ ps-tree@0.0.2 
│ │ └─┬ parse-table@0.0.0 
│ │   └── event-stream@0.5.3 
│ ├── timespan@2.0.1 
│ ├─┬ utile@0.0.10 
│ │ ├── mkdirp@0.3.0 
│ │ ├── ncp@0.2.5 
│ │ └── rimraf@1.0.9 
│ ├── watch@0.5.0 
│ └─┬ winston@0.5.10 
│   ├── colors@0.6.0-1 
│   ├── eyes@0.1.7 
│   ├─┬ loggly@0.3.11 
│   │ └── request@2.9.153 
│   └── stack-trace@0.0.6 
├── gzip@0.1.0 
├── knox@0.0.9 
├── less@1.2.2 
├── mint@0.3.0 
├─┬ mocha@0.14.1 
│ ├── commander@0.5.2 
│ ├── debug@0.5.0 
│ ├── diff@1.0.2 
│ ├── growl@1.5.0 
│ └─┬ jade@0.20.3 
│   └── mkdirp@0.3.0 
├── mongodb@0.9.9-4 
├─┬ pathfinder@0.3.0-1 
│ ├── detective@0.1.0 
│ ├─┬ findit@0.1.2 
│ │ └─┬ seq@0.3.5 
│ │   ├─┬ chainsaw@0.0.9 
│ │   │ └── traverse@0.3.9 
│ │   └─┬ hashish@0.0.4 
│ │     └── traverse@0.6.0 
│ ├── mime@1.2.5 
│ └── mkdirp@0.3.0 
├── redis@0.7.1 
├── sinon@1.3.1 
├─┬ stylus@0.24.0 
│ ├── cssom@0.2.2 
│ ├── debug@0.5.0 
│ ├── growl@1.4.1 
│ └── mkdirp@0.3.0 
├─┬ tower@0.3.9-9 
│ ├── URIjs@1.4.2 
│ ├── coffeekup@0.3.1 
│ ├── commander@0.5.2 
│ ├─┬ connect@2.0.2 
│ │ ├── debug@0.5.0 
│ │ ├── formidable@1.0.9 
│ │ └── mime@1.2.4 
│ ├── ejs@0.6.1 
│ ├─┬ express@2.5.8 
│ │ ├─┬ connect@1.8.5 
│ │ │ └── formidable@1.0.9 
│ │ ├── mime@1.2.4 
│ │ └── mkdirp@0.3.0 
│ ├── lingo@0.0.4 
│ ├── mime@1.2.5 
│ ├── moment@1.4.0 
│ ├── node-uuid@1.3.3 
│ ├── qs@0.4.2 
│ ├── restler@2.0.0 
│ ├─┬ socket.io@0.9.0 
│ │ ├── policyfile@0.0.4 
│ │ ├── redis@0.6.7 
│ │ └─┬ socket.io-client@0.9.0 
│ │   ├─┬ ws@0.4.0 
│ │   │ ├── commander@0.5.0 
│ │   │ └── options@0.0.2 
│ │   └── xmlhttprequest@1.2.2 
│ ├── underscore.logger@0.3.1 
│ └─┬ useragent@1.0.5 
│   ├── request@2.2.9 
│   ├── semver@1.0.12 
│   └── yamlparser@0.0.2 
├── uglify-js@1.2.5 
├── underscore@1.3.1 
└── underscore.string@2.0.0

どーん!

その数、依存パッケージをあわせて 194 個。容量は54MB。

さっそくコマンドから

インストール。ドーン。
$ npm install -g tower

プロジェクト作成。
$ tower new tower_sample

$ cd tower_sample/
$ npm install

ちょっとエラー出るので、sudo使う。
$ sudo npm install design.io

scaffold!すばらしい!
$ tower generate scaffold Post title:string body:test blongsTo:user
$ tower generate scaffold User email:string fisrtName:string lastName:string hasMany:posts

起動!
$ node server.js 

デバッグ。ドーン。
   info  - socket.io started
[Wed, 07 Mar 2012 13:34:56 GMT] INFO Tower development server listening on port 3000

info:  Name: design.io-watcher::tower_sample hook::listening                          Type: hook            Data: 5000
info:  Name: design.io-watcher::tower_sample hook::started                            Type: hook            Data: 5000
info:  Name: design.io-watcher::tower_sample hook::ready                              Type: hook            Data: {"name":"design.io-watcher::tower_sample","type":" ... 
info:  Name: design.io-watcher::tower_sample ready                                    Type: hook            Data: {"name":"design.io-watcher::tower_sample","type":" ... 
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/async.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/coffeekup.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/design.io.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/history.adapter.jquery.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/history.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/html5.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/mocha.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/moment.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/prettify.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/sinon.js
[Wed, 07 Mar 2012 13:35:01 GMT] INFO updated public/javascripts/vendor/javascripts/socket.io.js

(たくさん)

トップ

俺の名前をなぜ知っている。。。神か?

リスト

入力

圧巻の一言。ついにきたな、と。

ボク、まだコード書いてないよ。

これからはTower.jsで幸せになれるかもしれません。まる。

2012年3月6日火曜日

express.csrf でSafariでundefinedの再現

昨日の続き。express.csrf undefind問題の再現方法がようやくわかりました。Safariだけ。5.1.1をアップデートして5.1.3でも同様。

  1. Safari起動
  2. メニューから[Safari]→[Safariをリセット]→[リセット]
  3. https://localhost:3000/login にアクセス → session.csrf が発行される。
  4. ログインしてログアウト。
  5. ログイン画面 → session.csrf が undefined。以降ずっと。
  6. 再びリセットすると、最初の1回だけ session.csrf が発行されるも、3.と全く同じ値のcsrf。

やってみたこと

  • logout時に session.destroy → NG
  • 1.のとき、然るべきときに session.regenerate → NG
  • logout時に session.user だけ undefined設定 → OK
  • 3.のとき、session._csrf は 固定値になっている。そもそもChromeも。

もう眠いので推測。destoryやregenerateの挙動がブラウザで違うのは、HTTPヘッダがなんか足りない。session._csrf固定値なのは、express.csrf が static か singleton か ランダムじゃないのでは、と。この視点で明日コールドリーディングするか。

今日のコード

app.coffee

fs = require('fs')
express = require("express")
routes = require("./routes")
mongoose = require('mongoose')

app = module.exports = express.createServer
  key:fs.readFileSync 'key.pem'
  cert:fs.readFileSync 'cert.pem'

app.configure ->
  app.register ".coffee", require("coffeekup").adapters.express
  app.set "views", __dirname + "/views"
  app.set "view engine", "coffee"
  app.use express.cookieParser()
  app.use express.session
    secret: "your secret here"
    cookie: { secure: true }
  app.use express.bodyParser()
  app.use express.query()
  app.use express.methodOverride()
  app.use app.router
  app.use express.static(__dirname + "/public")
  app.use express.csrf()

app.dynamicHelpers token:(req, res) ->
  console.log(req.session._csrf)
  return req.session._csrf

app.configure "development", ->
  mongoose.connect 'mongodb://localhost/sample'
  app.use express.errorHandler(
    dumpExceptions: true
    showStack: true
  )

app.configure "production", ->
  mongoose.connect 'mongodb://localhost/sample'
  app.use express.errorHandler()

app.get "/", routes.index
app.get "/login", routes.login
app.post "/login", routes.authenticate
app.get "/secret", routes.secret
app.get "/signup", routes.signup
app.post "/signup", routes.regist
app.get "/logout", routes.logout
app.listen 3000
console.log "Express server listening on port %d in %s mode", app.address().port, app.settings.env

routes/index.coffee(logoutのところ)

  logout: (req, res, next) ->
    req.session.user = undefined
    res.redirect '/' 

2012年3月5日月曜日

express.csrf でSafariでundefinedの件

昨日の続き。Safariではundefined、Chromeでは出力された。

Safari

ログイン画面

Cookieがない。。。キャッシュクリア、再起動してもダメ。

Tokenはundefined。

Chrome

ログイン画面

Cookieがちゃんとある。キャッシュクリア、再起動でもCookieある。

Tokenもちゃんとある。

さて、何が原因なのか。X-CSRF-Tokenあたりだろうか。僕が仕様を理解していないのだろうなあ。