スマホアプリからサーバに接続してデータ取得

     以前から記事に書いている三目並べプログラムをとりあえずandroidアプリにしてみたのですが、単なる三目並べだとつまらないので一手の時間制限を設けて連続無敗記録を保存するように変更しました。そして今度はその記録をユーザ間で競ってもらおうと思い、サーバーを用意してスマホ上でランキング表示する機能をつけることにしました。
     ユーザ認証などの機能をつけると利用者側にとって敷居が高くなるので、適当なハンドルネームを登録してランキング表示するだけの機能です。単純な機能(スマホからハンドルネームを登録し、JSONPでランキングデータを取得する)なのでどうせなら今までやったことない方法でと思いNode.jsとMongoDBを使うことにしました。いろいろ印象に残ったことを書いてみます。

    今までにもあっても良さそうなのに無かったupsert

     いや、昔からどこかで使われていた機能かもしれませんが私は知りませんでした。InsertとUpdateを合わせてUpsert。document(recordとは言わないらしい)が存在していれば更新し、なければ新規登録する機能です。私の経験ではOracleにもSybaseにもSQL Serverにもなかったはずです。さすがNoSQLです。
     で、使ってみると新規にInsertした場合のid値をどうやって取得するのか悩みましたが、先にid値を生成してからUpsertを呼び出すようです。

          //uidで検索する例
          //新規とわかっている場合はuidにnullを入れてid生成
          var doc = {uid:null, name:username, score:unbeaten, date: null};
          var collection = db.collection("tictactoe");
          temp_id = new ObjectID(uid);
          collection.update({_id: temp_id}, {name:username, score:unbeaten, date: new Date()}, {upsert:true, w: 1}, function(err, result) {
            if (err) { throw err; }
            collection.findOne({_id: temp_id}, function(err, item) {
              if (err) { throw err; }
              if (item) {
                doc._id = item._id;
                doc.date = item.date;
              }
            });
          });
             
    

    出来るだけ新しいバージョンで開発環境を作る

     Nodeクックブックという本を参考にしていたのですが、ネット上に転がっていた古いコードから書き始めたのが間違いの元で、本のサンプルコードが動かない環境で一通り作った後に何度も同じ箇所を書き換えなければいけない羽目に陥りました。古いバージョンのままでも動けばいいのですが、何かトラブルに見舞われた時に「もしかしてバージョンが古いせいか?新しくしてみよう」となって直接トラブルとは関係ないのに書き直す羽目に陥ったということです。皆さんも気をつけて下さい。特にExpressはバージョンによって書式(構文)の変化が激しくて、バージョンが変わるともうそれは必要がなくなったというコードが多かったです。依存関係で深刻なものは無かったのですが、自分が使っているパッケージのバージョンは把握しておいた方が右往左往しなくて済みそうです。以下が現時点で使っているパッケージ(package.json)ファイルの一部です。

      , "dependencies": {
          "express": "4.12.1"
        , "jade": ">= 0.0.1"
        , "mongodb": "2.0.27"
      },
      "engines": {
        "node": "0.10.20",
        "npm": "2.8.3"
      },
    

     Expressで悩まされた一例として、本に記載されている通り以下のようにlessファイルを指定したのですが

    //app.js
    app.use(require('less-middleware')({
       src: __dirname + '/views',
       dest: __dirname + '/public',
       compress: true
    }));
    

     上手くいかなくて、ここに書いてある通り以下のように修正すれば動きました。

    //app.js
    app.use(require('less-middleware')(__dirname + '/public'));
    

     src:とdest:を分けて指定するのではなく、一つ(/public)だけ指定すればそこにあるlessファイルを読み込んで同じディレクトリにcssファイルを出力するという形で上手く機能しています。よく調べればオプションがあるのかも知れません。
     あと、Rubyのrvm環境同様にNodeも最初からnvmを使って複数のバージョンを切り替えられるようにしておいたほうがよさそうです。

    ローカルで動くのにHeroku上で動かない

     herokuで動かないのはMongoDBへの接続が出来ていないようだということはすぐわかったのですが、この記事を見つけるまで理由がわかりませんでした。ローカルではDBオブジェクトをnewしてServer.open、herokuではconnectを使うということのようです。ローカル環境にデータベースが存在しているわけじゃないんだからopen出来ると考える方がおかしいと言われそうですが、herokuもMongoDBもどこまで私の面倒を見てくれるのかわからないんだから、ローカルで動くんだからそのまま動くと考えてしまっても仕方がないと思います。
     で、記事の中で説明してくれているように、以下のようにDB接続モジュールだけ分割して

    // lib/MongoBuilder.js
    var mongo = require('mongodb');
    
    module.exports.ready = function(db_name, callback){
      if ( process.env.MONGOLAB_URI ){
        mongo.connect(process.env.MONGOLAB_URI, {}, function(err, db){
          callback(db);
        });
      }else{
        new mongo.Db('tictactoe', new mongo.Server('localhost', 27017), {safe: false}).open(function(err,db){
          callback(db);
        });
      }
    };
    

     以下のように使用すれば、

    // routes/index.js
    var MB = require("../lib/MongoBuilder");
    var users = new Array();
    exports.mypage = function(req, res){
      MB.ready('tictactoe', function(db){
        var collection = db.collection("tictactoe");
        collection.find().sort({score: -1}).limit(100).toArray(function(err, users){
          res.render('index', {title: 'Tictactoe Unbeaten', users: users});
        });
      });
    }
    
    

     ローカル環境でもheroku環境でも動くように出来ました。