苛烈!マングースは3度噛む
node.js のMongoDBドライバに Mongoose というものがあります。これの model.update 関数のコールバックの動きが分かりにくかったので、注意して様子を見てみました。
まず、ネイティブのREPLの動きを再確認します。
$ mongo MongoDB shell version: 2.2.1 connecting to: test // 新規データベースを作成 > use myBalanceBook switched to db myBalanceBook // 文書の作成 > db.girls.insert({name: 'honda tsubasa', spent: 500}) // 1つめの文書を作成 > db.girls.insert({name: 'mizuhara kiko', spent: 1500}) // 2つめ > db.girls.insert({name: 'nounen rena', spent: 20}) // 3つめ // 文書の書き換え > db.girls.update({name: 'nounen rena'}, {xxx: 'xxx'}) // 選択した1つの文書を上書き > db.girls.find() { "_id" : ObjectId("516a84a543b0013848d9a9ac"), "name" : "honda tsubasa", "spent" : 500 } { "_id" : ObjectId("516a84ce43b0013848d9a9ad"), "name" : "mizuhara kiko", "spent" : 1500 } { "_id" : ObjectId("516a84f543b0013848d9a9ae"), "xxx" : "xxx" } // 上書かれてしまった文書 // 文書にキーを追加 > db.girls.update({name: 'mizuhara kiko'}, {$set: {age: 22}}) // 選択した文書に、ageキーを追加 > db.girls.find() { "_id" : ObjectId("516a84a543b0013848d9a9ac"), "name" : "honda tsubasa", "spent" : 500 } { "_id" : ObjectId("516a84f543b0013848d9a9ae"), "xxx" : "xxx" } { "_id" : ObjectId("516a84ce43b0013848d9a9ad"), "age" : 22, "name" : "mizuhara kiko", "spent" : 1500 } // ageキーが追加された文書 // upsert(あればupdate、無ければinsert) > db.girls.update({name: 'kojima fujiko'}, {name: 'kojima fujiko', spent: 4}, true) // upsert(第3引数がtrue) > db.girls.find() { "_id" : ObjectId("516a84a543b0013848d9a9ac"), "name" : "honda tsubasa", "spent" : 500 } { "_id" : ObjectId("516a84f543b0013848d9a9ae"), "xxx" : "xxx" } { "_id" : ObjectId("516a84ce43b0013848d9a9ad"), "age" : 22, "name" : "mizuhara kiko", "spent" : 1500 } { "_id" : ObjectId("516a87be9a5e6b80d7b75284"), "name" : "kojima fujiko", "spent" : 4 } // upsert(insert)された文書 // multi update (複数の文書を書き換え) > db.girls.update({}, {$inc: {spent: 1000}}, false, true) // multi(第4引数がtrue):セレクタにヒットする全ての文書に対し、変更を加える // (ここでは、spentキーの値を1000インクリメントする) > db.girls.find() // 全ての文書のspentキーの値が1000増えている { "_id" : ObjectId("516a84a543b0013848d9a9ac"), "name" : "honda tsubasa", "spent" : 1500 } { "_id" : ObjectId("516a84f543b0013848d9a9ae"), "spent" : 1000, "xxx" : "xxx" } { "_id" : ObjectId("516a84ce43b0013848d9a9ad"), "age" : 22, "name" : "mizuhara kiko", "spent" : 2500 } { "_id" : ObjectId("516a87be9a5e6b80d7b75284"), "name" : "kojima fujiko", "spent" : 1004 } >
注意すべき点はここです。
- updateは、デフォルトでは1件のみを書き換える
- multiフラグ(第4引数)がtrueの場合、セレクタ(第1引数)にヒットした全ての文書を書き換える
それでは、NodeからMongoDBを操作するドライバmongoose の update関数の動きを見て行きます。
ドキュメント: http://mongoosejs.com/docs/api.html#model_Model.update
予め、上記データベースのgirlsコレクションを操作するためのGirlModelを作成しておきます。
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/myBalanceBook'); var GirlModel = mongoose.model('girls', { name: String, spent: Number, cantAfford: Boolean, age: Number })
それでは文書1件に対し、spentキーの値をインクリメントします。
GirlModel.update({name: 'mizuhara kiko'}, {$inc: {spent: 2000}}, function(err, numberAffected, raw) { console.log(err); // null console.log(numberAffected); // 1 console.log(raw); // { // updatedExisting: true, // n: 1, // connectionId: 48, // err: null, // ok: 1 // } });
1件の文書を操作しました。コールバック関数の引数には何が渡って来るのでしょうか?
第1引数からは、通常のNodeのコールバックと同じように、エラーが起きた場合エラーオブジェクトが来ます。
第2引数は、操作した文書の件数です。通常は0 か 1 が来るはずです。後述するmultiフラグをtrueにしない限り、書き換えは最大1件に対してしか行わないからです。
第3引数は、rawとありますから、生データのようです。このうち、
- updatedExisting: 何か1つでもセレクタにヒットしたらtrue
- n: 操作された文書の件数(コールバックの第2引数numberAffectedと同じ)
- connectionId: 知りません。どうせリクエストのUIDか何かでしょう。
- err: rawで返されるエラーオブジェクトのようです。
- ok: オッケーなら1、ダメなら0 のようです。試しにnumberAffected がゼロの結果でも、 ok は 1 でした。errと一体何が違うんだ。
続いて、mongooseから複数の文書をupdateしてみます。
GirlModel.update({ spent: { $gt: 50 } }, { $set: {cantAfford: true} }, { multi: true }, function(err, numberAffected, raw) { console.log(err); // null console.log(numberAffected); // 4 console.log(raw); // { // updatedExisting: true, // n: 4, // connectionId: 69, // err: null, // ok: 1 // } });
mongooseでは、multiなどのオプションを第3引数にオブジェクトでまとめて渡します。そのためコールバック関数は第4引数になります。multiフラグをtrueにしたので、操作された文書の件数:numberAffected が 4 になっているのが分かります。ふむふむ。
書いていて気づいたのですが、言うほど分かりにくくはなかったです。