カテゴリー : Mongoose

[Mongoose] ObjectId のバリデーションには mongoose.Types.ObjectId.isValid を使おう

Mongoose で ObjectId のバリデーションをするには mongoose.Types.ObjectId.isValid というメソッドがあるのでこれを使いましょう。

var mongoose = require('mongoose');
 
console.log(mongoose.Types.ObjectId.isValid);
// [Function: isValid]
 
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// true
 
console.log(mongoose.Types.ObjectId.isValid('spam'));
// false

参考情報

Best way to validate an ObjectId · Issue #1959 · LearnBoost/mongoose

[Mongoose] 特定の field が変更されたかは isDirectModified, isModified, modifiedPaths で確認できる

Mongoose では document の特定の field に変更があったかどうかを isDirectModified や isModified, modifiedPaths などの method で確認できます。

Ruby on Rails の ActiveRecord でいうところの changed? や 属性名_changed? と似たような機能が Mongoose でも提供されています。

では、1つずつ紹介していきましょう。

Document#isDirectModified(path)

isDirectModified メソッドは引数に指定した field 自身が変更されていれば true をそうでなければ false を返します。

doc.set('documents.0.title', 'changed');
 
// 'documents.0.title' 自体が変更されているので true を返す
doc.isDirectModified('documents.0.title') // true
 
// 'documents' の下の階層で変更されているが 'documents' 自体は変更されていないので false を返す
doc.isDirectModified('documents') // false

Document#isModified([path])

isModified メソッドは引数に指定した field に関係がある箇所が変更されていれば true をそうでなければ false を返します。

doc.set('documents.0.title', 'changed');
 
// 変更された field とそれを含む上の階層は true を返す
doc.isModified()                    // true
doc.isModified('documents')         // true
doc.isModified('documents.0.title') // true
 
// 変更されてない field を指定すると false を返す
doc.isModified('something')         // false
 
// 'documents' の下の階層で変更されているが 'documents' 自体は変更されていないので false を返す
doc.isDirectModified('documents')   // false

Document#modifiedPaths()

modifiedPaths メソッドは変更があった field 名を Array で返します。

doc.set('name', 'test name');
doc.set('documents.0.title', 'changed');
 
doc.modifiedPaths()
[ 'name',
  'documents.0.title' ]

これらの機能をうまく使ってロジックをすっきり書けるといいですね。

[Mongoose] TypeError: Cannot read property ‘options’ of undefined

Mongoose でスキーマ定義に type: ObjectId としている field にオブジェクト型のデータが入ってると TypeError: Cannot read property ‘options’ of undefined エラーが発生します。

TypeError: Cannot read property 'options' of undefined
  at ObjectId.cast (/u/apps/com/shared/node_modules/mongoose/lib/schema/objectid.js:99:22)
  at /u/apps/com/shared/node_modules/mongoose/lib/document.js:288:29
  at model.Document.$__try (/u/apps/com/shared/node_modules/mongoose/lib/document.js:769:8)
  at init (/u/apps/com/shared/node_modules/mongoose/lib/document.js:287:16)
  at model.Document.init (/u/apps/com/shared/node_modules/mongoose/lib/document.js:246:3)
  at completeOne (/u/apps/com/shared/node_modules/mongoose/lib/query.js:1392:10)
  at Promise.<anonymous> (/u/apps/com/shared/node_modules/mongoose/lib/query.js:1160:11)
  at Promise.<anonymous> (/u/apps/com/shared/node_modules/mongoose/node_modules/mpromise/lib/promise.js:177:8)
  at Promise.EventEmitter.emit (events.js:95:17)
  at Promise.emit (/u/apps/com/shared/node_modules/mongoose/node_modules/mpromise/lib/promise.js:84:38)
  at Promise.fulfill (/u/apps/com/shared/node_modules/mongoose/node_modules/mpromise/lib/promise.js:97:20)
  at Promise.resolve (/u/apps/com/shared/node_modules/mongoose/lib/promise.js:114:23)
  at /u/apps/com/shared/node_modules/mongoose/lib/model.js:2029:23
  at process._tickCallback (node.js:415:13)

再現手順ですが、例えば下記のようなスキーマ定義で、

Article = new Schema
  user:
    type: ObjectId
    ref: 'User'

user filed に ObjectId ではなく { name : ‘hoge’ } のような値を入れて、save するとエラーが発生します。

article = new Article
article.user = { name : 'hoge' } // ObjectId じゃない!
article.save()->

findAndModify などの Mongo DB Native NodeJS Driver を直接呼ぶメソッド findOneAndUpdate メソッドを使ったり、mongo shell で直接データを編集したりするとこういうエンバグさせてしまうので、必要ない限りやめたいですね。


参考情報

findOneAndUpdate – Mongoose API v3.8.18

findAndModify — MongoDB Manual 2.6.4

[Mongoose] stream を使ってバッチ処理するときは noCursorTimeout: true オプションを設定すると幸せになれるかも

Express.js(Node.js) + Mongoose(MongoDB) という構成で、バッチ処理を長時間実行すると途中で終了してしまう問題が発生しました。

状況としては、まだ cursor が次のデータを取得できるはずなのに、stream.on ‘data’ 内では次のデータが渡ってこなくて、stream.on ‘error’ が呼ばれることなく、stream.on ‘close’ が呼ばれている感じです。cursor がタイムアウトしてしまっているのが原因らしいです。

解決方法としては、第三引数に noCursorTimeout: true を指定することで、途中で終了せずにバッチ処理を最後まで実行することができました。

var stream = User.find(
  {},
  {},
  { noCursorTimeout: true }
).stream();
 
stream.on('data', function (doc) {
  // do something with the mongoose document
}).on('error', function (err) {
  // handle the error
}).on('close', function () {
  // the stream is closed
});

参考情報

[Node.js] Mongoose で簡易的な auto increment を実装する

Node.js + Mongoose で auto increment の実装をメモ。

/**
 * 新規登録時に使う code を取得する
 *
 * @param {Function} callback(error, code)
 **/
var getNewCode = function (callback){
  var new_code = 1;
 
  Books.findOne({}, {}, {
    sort: {
      code: -1
    }
  }, function(error, doc){
    if (error) {
      console.error(error.stack || error);
      return callback(error);
    }
 
    if (!doc || !doc.code) {
      return callback(null, new_code);
    }
 
    new_code = doc.code + 1;
 
    return callback(null, new_code);
  });
};

本当は参考記事のように別途、モデルでシーケンシャルな code (id) を管理した方がいいのでしょうけど、そこまでは必要ないかなと用途で書きました。

連番を管理するためのモデルを作るとデータも線形的に増えるし、メンテも面倒になりますしね。


参考情報

Two hats: Mongooseでauto incrementの実装

[Mongoose] how to get virtual attribute for each nested object in an array of objects?

参考情報

node.js – Node-Express Mongoose: how to get virtual attribute for each nested object in an array of objects? – Stack Overflow

[Mongoose] 配列形式のサブドキュメント内で aggregate

Mongoose で、配列形式のサブドキュメント内で aggregate する方法をメモ。

mapreduce – Mongodb aggregate on subdocument in array – Stack Overflow

[Mongoose] 異なるOR条件を2つ以上指定したい場合の書き方

Node.js + Mongoose にて、異なるOR条件を2つ以上指定したい場合の書き方をメモ。

Test.find({
  $and: [
    { $or: [{a: 1}, {b: 1}] },
    { $or: [{c: 1}, {d: 1}] }
  ]
}, function (err, results) {
  // do something
}

$and を使って、明示的に $or を複数組み合わせて指定するだけでOKです。

参考情報

node.js – Combine two OR-queries with AND in Mongoose – Stack Overflow

[Mongoose] Schema.Types.Mixed だと save 前に .markModified が必要

Node.js + Mongoose でインスタンスを save で保存できないときの原因のひとつに Schema.Types.Mixed があります。

プロパティの型が Schema.Types.Mixed とかだと、値に変更があったことを Mongoose が自動検知してくれないみたいです。

To “tell” Mongoose that the value of a Mixed type has changed, call
the .markModified(path) method of the document passing the path to the
Mixed type you just changed.

[引用元]:Mongoose SchemaTypes v3.5.6

markModified メソッドの使い方は下記のような感じです。

var schema = new Schema({
  mixed:   Schema.Types.Mixed
})
 
// example use
 
var Thing = mongoose.model('Thing', schema);
 
var m = new Thing;
m.mixed = {[ any: { thing: 'i want' } ]};
m.markModified('mixed');
m.save(callback);