node.js製フレームワークRailwayJSで遊んでみる

こんばんは。


台風の今夜いかがお過ごしでしょうか。こちらは結構ヤバイです。今日の天気とは関係ありませんが、javascript界隈も4号以上にヤバク熱い感じになってきてます。


今日もはてブホッテントリ

http://www.publickey1.jp/blog/12/javascript_mvc.html

みたいな記事が入ってきてます。



というわけでサーバーサイドJS本命node.jsの上に乗っかったRailwayJSを使ってサンプルアプリを作ってみました。ちょろちょろとは触ってたけど、わりと普通の構成のWEBアプリ作ったらどうなるの?っていう興味本位andブラ三の合成レシピをスマホで見たかったてのも理由です。


作成したアプリはhttp://csv2sl.ueebee.com/
Excelシートをコピペすると、HTMLのテーブルに変換してページを置いておけるだけというシンプルなものです。


普通ぽいWEBアプリをnode.jsで作ってみた私個人の感想は後ほど記載します。

RailwayJSは

本家:http://railwayjs.com/
日本語:http://railwayjs.jp/

で最新の情報を得ることができます。

「Create NodeJS web apps faster with more energy, using RailwayJS」

とあってnodejs製のWEBアプリ作成を助けてくれるフレームワークです。
簡易フレームワークexpressに、Ruby on Railsの便利な機能を乗っけてきたような感じになっています。

ちなみに同じ位置づけのnode.js製フレームワークとしてはtower.jsとかです。
http://towerjs.org/

いずれもRuby on Railsを強く意識しているフレームワークだな~という感想です。

RailwayJSを選んだポイント

デフォルトでcoffee-scriptで書けるらしい(tower.jsもらしいです)
最初tower.jsに行ったんだけど・・・scaffoldingが初見で動かなかったorz
直感&なんとなく( ゜д゜)ハッ!


という理由です。

インストール

node.jsが使える環境なら、

$ npm install railway -g

とするだけです。

node.jsの環境構築等はいっぱい情報があるので省略。

アプリの雛形作成

こちらもRuby on Railsよろしく、アプリケーションの雛形を作成してくれます。
ずらずら-とファイルが作成されます。
※coffee-scriptを使うので--coffeeオプションを追加しています。

$ railway init csv2sortableList --coffee

作業ディレクトリへの移動・必要なパッケージのインストール

作成されたディレクトリに移動して、関連パッケージをインストールします。

$ cd csv2sortableList
$ npm install -l

ここまでで最低限必要な作業は終了。
ここからアプリの実装に必要な作業に入ります。

scaffoldを使って基本枠を作る

$ railway g scaffold post title content updated_at

これだけで動くアプリができます。
controllerもviewもlayoutsも、Ruby on Railsと同じ構成になっています。
また中身は空っぽですがhelper、modelも同じ構成です。

サーバー起動

以下のコマンドでサーバー起動します。

$ railway s ポート番号

※デフォルトだと3000番でサーバ起動します。

CRUD動作が行えることを確認できます。
見た目もTwitterBootStrapがデフォルトで適用されているため、おしゃれな画面になってます。


Controllerの中身

以下の感じです。CoffeeScriptなのも手伝って、かなり見通しが良くなってます。

注目するポイントとしては、
before ->
のところでここがbefore_filterのような共通化したコードになっています。
, only: ['show', 'edit', 'update', 'destroy']
で適用するアクションを指定しています。


load  'application'

before 'load post', ->
    Post.find params.id, (err, post) =>
        if err
            redirect path_to.posts()
        else
            @post = post
            next()
, only: ['show', 'edit', 'update', 'destroy']

action 'new', ->
    @post = new Post
    @title = 'New post'
    render()

action 'create', ->
    Post.create body.Post, (err, post) =>
        if err
            flash 'error', 'Post can not be created'
            @post = post
            @title = 'New post'
            render 'new'
        else
            flash 'info', 'Post created'
            redirect path_to.posts()

action 'index', ->
    Post.all (err, posts) =>
        @posts = posts
        @title = 'Posts index'
        render()

action 'show', ->
    @title = 'Post show'
    render()

action 'edit', ->
    @title = 'Post edit'
    render()

action 'update', ->
    @post.updateAttributes body.Post, (err) =>
        if !err
            flash 'info', 'Post updated'
            redirect path_to.post(@post)
        else
            flash 'error', 'Post can not be updated'
            @title = 'Edit post details'
            render 'edit'

action 'destroy', ->
    @post.destroy (error) ->
        if error
            flash 'error', 'Can not destroy post'
        else
            flash 'info', 'Post successfully removed'
        send "'" + path_to.posts() + "'"


@post.save (err) =>
みたいなコードが見受けられますが、ORMとして

https://github.com/1602/jugglingdb

jugglingdbというORMを採用しています。
対応DBとしても

redis
mongodb

等が揃っており、使い勝手がいいです。(sqlite3、rowlevel-mysql等も使えるようですが試してません)
redis、mongodbとDBを複数使用できるのもポイント高いです。

ちなみにRailwayJSの公式サイトではORMの説明が弱いと感じます。jugglingdb https://github.com/1602/jugglingdb
を確認したほうが使い方の参考になりました。

rails console相当の「railway c」

こちらもRuby on Railsのrails consoleに相当する、「railway c」とすると対話型コンソール(REPL console)が利用できます。

railway c
railway> User.find(53, c)
Callback called with 2 arguments:
_0 = null
_1 = [object Object]
railway> _1
{ email: [Getter/Setter],
  password: [Getter/Setter],
  activationCode: [Getter/Setter],
  activated: [Getter/Setter],
  forcePassChange: [Getter/Setter],
  isAdmin: [Getter/Setter],
  id: [Getter/Setter] }

というような感じになります。コンソールはjavascriptのcallbackを取るスタイルに多少なれないと行けないかもしれません。
ループも含めコールバックを使う形になります。

helper

こちらもRuby on Railsのヘルパーに似てます。組み込みヘルパーは本家RailwayJSにだいたい書いてあります。
今回時刻表示のフォーマットヘルパーがどこにあるのかわからなかったので、以下の共通ヘルパーを作ってみました。
※ここは自動で生成されたものだとcoffee-scriptじゃないみたいです。

app/helpers/application_helper.js

require('date-utils');

module.exports = {
   my_date_format_helper: function(dateString){

          if (dateString == null || dateString == undefined){
                 return "";
          } else {
                 var formatted = dateString.toFormat("YYYY-MM-DD HH24:MI");
                 return formatted;
          }
   }
};

view側で

<%- my_date_format_helper(@post.updated_at) %>

で使用できます。

routes

アプリのURI管理は
config/routes.js
に書いていきます。

exports.routes = function (map) {
    map.resources('users');
    map.root('posts#index');
    map.resources('posts');


    map.get("postsall", "postsall#index");
    map.get('tos', 'static_page#tos');
    // Generic routes. Add all your routes below this line
    // feel free to remove generic routes
    map.all(':controller/:action');
    map.all(':controller/:action/:id');
};

これもほぼRuby on Railsのroutes.rbと同じです。
map.root:アプリトップを指定
map.resources:RESTなURI
map.get:該当するURIに対するコントローラ・アクション
etcetc...



パッケージ管理

プロジェクト直下に

package.json

というファイルがあります。ここに必要なパッケージを書いていく感じになります。
これもBundlerに似てます。

{ "name": "csv2sl"
, "version": "0.0.1"
, "engines": ["node >= 0.4.0"]
, "main": "server.js"
, "dependencies":
  { "ejs":              "*"
  , "ejs-ext":          "*"
  , "express":          "~2.x"
  , "railway":          ">= 0.2.6"
  , "jugglingdb":       ">= 0.1.0"
  , "coffee-script":    ">= 1.1.1"
  , "csv":              ""
  , "mongoose":         ""
  , "async":            ""
  , "password-hash":    ""
  , "everyauth":        ""
  , "jade":             ""
  , "date-utils":       "*"
  , "js2coffee":        "*"
  },
  "devDependencies":
  { "nodeunit":         "*"
  , "sinon":            "*"
  , "semicov":          "*"
  }
, "scripts":
  { "test": "nodeunit test/*/*"
  }
}


書いたパッケージのインストールは

$ npm install -l

で依存関係含めインストールしてくれます。


ざっくりとはこんな感じになります。

気をつけるところ

node.jsは基本非同期です。
なので、

hoge = []
for sheet in sheets
    hoge.push sheet
render({hoge: hoge})

みたいなコードを書くと、Viewに変数hogeの値は[]で渡っちゃうことがあります。
なので、組み込みのnext()をうまく使うか、async.jsやstep.js等を使ってあげると楽できます。
今回はasync.jsを使ってみました。

ただそもそも非同期前提なんだしwebSocket楽に導入できるんだから、
・細かくコントローラを切って非同期で値の受け渡しをする
というのもありかなとおもいます。

その他

今回使ったライブラリは以下のとおりです。

csvパーサー
csv.js
https://github.com/wdavidw/node-csv-parser


フロー制御
async.js
https://github.com/caolan/async


oauth認証ライブラリ
everyauth
https://github.com/bnoguchi/everyauth


日付フォーマット整形
date-utils
https://github.com/JerrySievert/node-date-utils


テンプレート
jade
http://jade-lang.com/
※RailwayJSは基本はejsです。

使ってみた感想

CRUDなアプリも作れるけど、実用的な細かいものを考えるとちょっと成熟してない
やっぱりイベントループなことを考えると今のところはアプリケーションサーバの一部に使うのが吉(websocket鯖とか)
一部だけ使うって考えると、軽量フレームワークexpressが情報量が多いのもなるほど~という感じ
javascript大好きだぜ~!という人が一杯いれば採用もありかも