mongooseで埋め込みを使う

MongoDBのリレーションでやるか埋め込みでやるか迷った末に埋め込みでやることにしたので、
mongooseでのやり方をざっと試してみた。

使ったバージョン

node.js v0.8.18
mongoose 3.5.5

試験ソース

コールバックだらけになるので、jsdeferred使って流れ整理した。

var Deferred = require('./deps/jsdeferred').Deferred,
// mongoose
mongoose = require('mongoose'),
// 子スキーマ
ChildSchema = new mongoose.Schema({
    name : String
}),
// 親スキーマ
ParentSchema = new mongoose.Schema({
    name : String,
    childlen : [ChildSchema]
}),
// 親モデル
Parent = mongoose.model('Parent', ParentSchema);

// 接続
mongoose.connect('mongodb://localhost/foo');

!function() {
    // 最初にdb.parentsを全部消しておく
    var d = new Deferred();
    Parent.remove(function(err) {
        if (err) {
            d.fail(err);
            return;
        }
        d.call();
    });
    return d;
}()
.next(function() {
    var d = new Deferred(),
    // parentを新規作成して保存
    parent = new Parent({
        name : 'test'
    });
    parent.save(function(err) {
        if (err) {
            d.fail(err);
            return;
        }
        d.call();
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        // 親の名前をチェック
        console.log(parent.name);
        console.log('子供が0人');
        // この時点ではまだ子供がいない
        console.log(parent.childlen);
        // 子供を一人追加
        parent.childlen.push({
            name : 'baby1'
        });
        parent.save(function(err) {
            if (err) {
                d.fail(err);
            }
            d.call();
        });
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        console.log('子供が1人');
        console.log(parent.childlen);
        // まとめて複数人追加
        parent.childlen.push({ 
            name : 'baby2'
        });
        parent.childlen.push({ 
            name : 'baby3'
        });
        parent.childlen.push({ 
            name : 'baby4'
        });
        parent.save(function(err) {
            if (err) {
                d.fail(err);
            }
            d.call();
        });
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        console.log('子供が4人');
        console.log(parent.childlen);

        // 名前を更新する
        parent.childlen[0].name = 'John';
        parent.childlen[1].name = 'Paul';
        parent.childlen[2].name = 'George';
        parent.childlen[3].name = 'Ringo';
        parent.save(function(err) {
            if (err) {
                d.fail(err);
            }
            d.call();
        });
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        console.log('更新後');
        console.log(parent.childlen);
        // 1つ削除
        parent.childlen.remove(parent.childlen[1]);
        parent.save(function(err) {
            if (err) {
                d.fail(err);
            }
            d.call();
        });
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        console.log('1件削除後');
        console.log(parent.childlen);
        // 全て削除する
        /** 
         * forEachだとうまく消せない
         */
        // parent.childlen.forEach(function(c) {
        //     c.remove();
        // });
        var i, len = parent.childlen.length;
        for (i = len - 1; i >= 0; i--) {
            parent.childlen[i].remove();
        }
        parent.save(function(err) {
            if (err) {
                d.fail(err);
            }
            d.call();
        });
    });
    return d;
})
.next(function() {
    var d = new Deferred();
    // 親を取得
    Parent.findOne({ name : 'test'}, function(err, parent) {
        if (err) {
            d.fail(err);
            return;
        }
        console.log('0人');
        console.log(parent.childlen);
        d.call();
    });
    return d;
})
.next(function() {
    mongoose.disconnect();
})
.error(function(err) {
    console.log(err);    
    mongoose.disconnect();
});

実行結果

$ node index
test
子供が0人
[]
子供が1人
[{ name: 'baby1', _id: 51110eba0d42bc5711000003 }]
子供が4人
[{ name: 'baby1', _id: 51110eba0d42bc5711000003 }
{ name: 'baby2', _id: 51110eba0d42bc5711000004 }
{ name: 'baby3', _id: 51110eba0d42bc5711000005 }
{ name: 'baby4', _id: 51110eba0d42bc5711000006 }]
更新後
[{ _id: 51110eba0d42bc5711000003, name: 'John' }
{ _id: 51110eba0d42bc5711000004, name: 'Paul' }
{ _id: 51110eba0d42bc5711000005, name: 'George' }
{ _id: 51110eba0d42bc5711000006, name: 'Ringo' }]
1件削除後
[{ _id: 51110eba0d42bc5711000003, name: 'John' }
{ _id: 51110eba0d42bc5711000005, name: 'George' }
{ _id: 51110eba0d42bc5711000006, name: 'Ringo' }]
0人
[]

感想

削除の時にforEachで回すとインデックスの関係か削除うまくいかないみたい。