node.js Express3.xのViewHelperメソッドでSessionの値を使う。

Express2.xとExpress3.xでは、仕様が大きく変わっているようで、2.xのコードを参考に3.xでコードを書いていると思いがけないところでハマってしまいます。

今回はViewHelperメソッドでSessionの値を使う方法でハマってしまいました。
なお、今回紹介するコード実装したExpressバージョンは 3.4.8 です。

ViewHelperメソッドとは?

まず、ViewHelperメソッドについて簡単に説明します。
本記事ではViewの複数箇所で使われる処理を共通化したメソッド群をViewHelperメソッドとよびます。
具体的には下記のようなコードです。

lib/index.js

// View Helper
exports.helpers = {
	link_to: function(name, url){
		return '<a href="' + url + '">' + name + '</a>';
	},
	text_format: function(text){
		return text.replace(/ /g, '&nbsp').replace(/\r\n|\n|\r/g, '<br />');
	}
};

app.jsにてapp.localsメソッドの引数にViewHelperメソッドオブジェクトを渡すとViewからViewHelperメソッドを呼び出せるようになります。

app.js

var express = require('express'),
	routes = require('./routes'),
	lib = require('./lib');
/* 〜省略〜 */
app.locals(lib.helpers);
/* 〜省略〜 */

views/topics/show.ejs

<!-- 〜省略〜 -->
<h2 class="title">
  <%- link_to(post.title, '/topics/' + topic_id + '/posts/' + post._id) %>
</h2>
<div class="detail"><%- text_format(escape(post.detail)) %></div>
<!-- 〜省略〜 -->

ViewHelperメソッドでSessionを使って実現したい機能。

今回は、Sessionの値を使って次のような機能を実装します。
1.Sessionの値でユーザがログイン中かどうか判断する。
2.ログイン中の場合はユーザ名とログアウトを表示する。
3.ログイン中ではない場合はログインを表示する。
次のコードはエラーになりますが、実装イメージはこんな感じです。

lib/index.js

// View Helper
exports.helpers = {
	link_to: function(name, url){
		return '<a href="' + url + '">' + name + '</a>';
	},
	text_format: function(text){
		return text.replace(/ /g, '&nbsp').replace(/\r\n|\n|\r/g, '<br />');
	}
	username_or_login: function() {
		// このままではsessionは定義されていないためエラーになる。
		if(session.username){
			return '' +
			'<p class="login_user">Login as ' + session.username + '</p>' +
			'<p class="logout"><a href="/sessions/destroy">logout</a></p>';
		}
		return '<p class="login"><a href="/sessions/new">login</a></p>';
	}

};

ViewHelperメソッドでSessionの値を使う実装。

ViewHelperメソッドでもSessionの値を使えるように修正を加えていきます。

app.js

var express = require('express'),
	routes = require('./routes'),
	lib = require('./lib');
var viewSession = {};
/* 〜省略〜 */
app.use(express.cookieParser('your secret here'));
app.use(express.session());
app.use(function(req, res, next){
	// これだとviewSessionにreq.sessionの参照が代入され、
	// Viewではreq.sessionが開放されてしまうためエラーになる。
	// viewSession = req.session;
	viewSession.username = req.session.username ? req.session.username : '';
	next();
});
/* 〜省略〜 */
app.locals(lib.helpers(viewSession));
/* 〜省略〜 */

lib/index.js

exports.helpers = function(session){
	return {
		link_to: function(name, url){
			return '<a href="' + url + '">' + name + '</a>';
		},
		text_format: function(text){
			return text.replace(/ /g, '&nbsp').replace(/\r\n|\n|\r/g, '<br />');
		},
		username_or_login: function() {
			if(session.username){
				return '' +
				'<p class="login_user">Login as ' + session.username + '</p>' +
				'<p class="logout"><a href="/sessions/destroy">logout</a></p>';
			}
			return '<p class="login"><a href="/sessions/new">login</a></p>';
		}
	};
};

views/topics/show.ejs

<!-- 〜省略〜 -->
<div>
  <h1><a href="/">Node Express</a></h1>
  <div><%- username_or_login() %></div>
</div>
<!-- 〜省略〜 -->
<h2 class="title">
  <%- link_to(post.title, '/topics/' + topic_id + '/posts/' + post._id) %>
</h2>
<div class="detail"><%- text_format(escape(post.detail)) %></div>
<!-- 〜省略〜 -->

ちょっと強引かもしれませんがこれでViewでもSessionの値を使うことができます。

2.xではどのように実装するか。

2.xでは req,res を受け取れる app.dynamicHelpers で実現出来ていました。
また、次のサイト app.locals.use を使った方法が紹介されていますが、 app.locals.use は3.xのどこかのバージョンでなくなってしまっています。
A Node in Nodes - 日本最速express3入門

課題

Sessionの値が増える毎にapp.jsを編集しなければならない。

今回紹介した方法では、viewSessionのプロパティにいちいち値を設定しているのでViewHelperメソッドで使いたいSessionの値が増える度にapp.jsの修正が必要になります。
req.sessionオブジェクトをviewSessionにCloneしたいのですが、javascriptでオブジェクトをcloneする標準の方法はないようです。(jQueryにはあるようです。)
javascriptのオブジェクトのcloneについて詳しくは次の記事を参照下さい。
JavaScriptのオブジェクトをコピーする - Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

最後に

もっとスマートは方法があればコメント頂けると幸いです。

参考図書

サーバサイドJavaScript Node.js入門

サーバサイドJavaScript Node.js入門