pig's diary

何でも忘れるので万年初心者ね

苛烈!マングースは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 になっているのが分かります。ふむふむ。

書いていて気づいたのですが、言うほど分かりにくくはなかったです。