2012年12月12日水曜日

Node.jsが面白い件⑥ MongoDBで遊ぶ 〜Mongoose編その③:サブドキュメントとリファレンス〜

前回に引き続き、今回はサブドキュメントとリファレンスについてです。MongoDBではEmbedding and Linkingとして解説されており、Joinを持たないMongoDBの設計手法の1つとしてよく使われます。

2. sub-docs

MongooseではSchemaを定義してドキュメントを操作しますが、別のドキュメント配列をSchemaの一部として保持することができます。以下、これまで使用していたuserSchemaにhistSchemaというドキュメントを持たせました。

    // SubDocument Schema
    var operations = 'create,submit,cancel'.split(',');
    var histSchema = mongoose.Schema({
        operation : { type: String, enum: operations },
        date: Date
    });

    // サンプルスキーマ
    var userSchema  = mongoose.Schema({
        uid : { type: String, index: {unique:true} },
        name: { first: String, last: String },
        age : { type: Number, min: 1 },
        gender : { type: String, enum: genders },
        histories : [ histSchema ]
    });

    var User = db.model('User', userSchema);

userドキュメント作成の際に、historiesの値を入れます。

    User.create(
      { uid: 'kat',
        name: {first: 'KAT', last:'INTINK'},
        age: 100,
        gender: 'male',
        histories: [{ operation : 'create', date: new Date }]
      }, (以下省略)

値を表示すると以下のように_idが自動生成され、ドキュメントとして保持されていることがわかります。
{ uid: 'kat',
  age: 100,
  gender: 'male',
  _id: 50c5fa1329c6e5d70c000002,
  __v: 0,
  histories: 
   [ { operation: 'create',
       date: Tue Dec 11 2012 00:04:51 GMT+0900 (JST),
       _id: 50c5fa1329c6e5d70c000003 } ],
  name: { first: 'KAT', last: 'INTINK' } }

さらにドキュメントを追加します。

user.histories.push({ operation: 'submit', date: new Date });
user.save (function (err) { (以下省略)

{ uid: 'kat',
  age: 100,
  gender: 'male',
  _id: 50c5fae2afa091e00c000002,
  __v: 1,
  histories: 
   [ { operation: 'create',
       date: Tue Dec 11 2012 00:04:51 GMT+0900 (JST),
       _id: 50c5fa1329c6e5d70c000003 },
     { operation: 'submit',
       date: Tue Dec 11 2012 00:08:18 GMT+0900 (JST),
       _id: 50c5fae2afa091e00c000004 } ],
  name: { first: 'KAT', last: 'INTINK' } }

内部に保持されるドキュメントなので、あるオブジェクトが複数の固有のドキュメントを持つような場合に使えます(例だとユーザの操作ログを保持しています)。外部のドキュメントを参照したい場合は、次に説明するリファレンスを使います。

3. population

以下のようなSchemaを考えます。

    // itemSchema
    var itemSchema = new mongoose.Schema({
      name : String,
      color : String
    });

    // サンプルスキーマ
    var userSchema  = mongoose.Schema({
      uid : { type: String, index: {unique:true} },
      name: { first: String, last: String },
      age : { type: Number, min: 1 },
      gender : { type: String, enum: genders },
      histories : [ histSchema ],
      items : [ { type: mongoose.Schema.Types.ObjectId, ref: 'Item' } ]
    });

    var Item = db.model('Item', itemSchema);
    var User = db.model('User', userSchema);

sub-docsで使用したuserSchemaに、itemsを持たせました。ただし、itemsはitemSchemaとして定義されているモデルItemを参照しています。それではドキュメントを作ってみましょう。まずはitemから。

    Item.create(
      { name : 'table', color : 'black' },
      function(err, item1) {
        if (err) {
          console.log('[error] create item');
          console.log(err);
        } else {
          Item.create(
            { name: 'chair', color: 'white' },
            function(err, item2) { (以下省略)

上記のようにitemを2つ作った後、Userにこれら2つのドキュメントをセットします。Userを作ったあとsub-docsと同様にpushします。

    User.create(
      { uid: 'kat',
        name: {first: 'KAT', last:'INTINK'},
        age: 100,
        gender: 'male',
        histories: [{ operation : 'create', date: new Date }],
      }, function(err, user) { 

(省略)

                  user.items.push(item1);
                  user.items.push(item2);

                  user.save (function(err) {


(以下省略)

結果を見てみます。まずはUserを見ると、以下のように格納されています。

User.findOne().exec(

{ uid: 'kat',
  age: 100,
  gender: 'male',
  _id: 50c886e21f32c23d18000004,
  __v: 2,
  items: [ 50c886e21f32c23d18000002, 50c886e21f32c23d18000003 ],
  histories: 
   [ { operation: 'create',
       date: Wed Dec 12 2012 22:30:10 GMT+0900 (JST),
       _id: 50c886e21f32c23d18000005 },
     { operation: 'submit',
       date: Wed Dec 12 2012 22:30:10 GMT+0900 (JST),
       _id: 50c886e21f32c23d18000006 } ],
  name: { first: 'KAT', last: 'INTINK' } }

itemsに表示されている結果は、pushにより格納されたitem1、item2の_idです。これだけだと単にitemの_idを保持しているだけですが、mongooseではpopulateを使ってJoinのように各_idに相当する値を一緒に取得することができます。

User.findOne().populate('items').exec(function(err, items) {

この結果は以下のようになります。

{ __v: 2,
  _id: 50c886e21f32c23d18000004,
  age: 100,
  gender: 'male',
  uid: 'kat',
  items: 
   [ { name: 'table',
       color: 'black',
       _id: 50c886e21f32c23d18000002,
       __v: 0 },
     { name: 'chair',
       color: 'white',
       _id: 50c886e21f32c23d18000003,
       __v: 0 } ],
  histories: 
   [ { operation: 'create',
       date: Wed Dec 12 2012 22:30:10 GMT+0900 (JST),
       _id: 50c886e21f32c23d18000005 },
     { operation: 'submit',
       date: Wed Dec 12 2012 22:30:10 GMT+0900 (JST),
       _id: 50c886e21f32c23d18000006 } ],
  name: { first: 'KAT', last: 'INTINK' } }

itemsの中身が、_idだけではなくname, colorの値も表示されました。このように、Joinに似た操作を使ってより自然なドキュメントの結果を扱うことができます。

以上、ドキュメント内で他のドキュメントを扱うことでBlogとコメントなど自然な形のオブジェクトを表現することが可能になります。

0 件のコメント:

コメントを投稿