Multiple schema references in single schema array - mongoose Multiple schema references in single schema array - mongoose mongoose mongoose

Multiple schema references in single schema array - mongoose


What you are looking for here is the mongoose .discriminator() method. This basically allows you to store objects of different types in the same collection, but have them as distinquishable first class objects.

Note that the "same collection" principle here is important to how .populate() works and the definition of the reference in the containing model. Since you really can only point to "one" model for a reference anyway, but there is some other magic that can make one model appear as many.

Example listing:

var util = require('util'),    async = require('async'),    mongoose = require('mongoose'),    Schema = mongoose.Schema;mongoose.connect('mongodb://localhost/gunshow');//mongoose.set("debug",true);var scenarioSchema = new Schema({  "name": String,  "guns": [{ "type": Schema.Types.ObjectId, "ref": "Gun" }]});function BaseSchema() {  Schema.apply(this, arguments);  // Common Gun stuff  this.add({    "createdAt": { "type": Date, "default": Date.now }  });}util.inherits(BaseSchema, Schema);var gunSchema = new BaseSchema();var ak47Schema = new BaseSchema({  // Ak74 stuff});ak47Schema.methods.shoot = function() {  return "Crack!Crack";};var m16Schema = new BaseSchema({  // M16 Stuff});m16Schema.methods.shoot = function() {  return "Blam!!"};var Scenario = mongoose.model("Scenario", scenarioSchema);var Gun = mongoose.model("Gun", gunSchema );var Ak47 = Gun.discriminator("Ak47", ak47Schema );var M16 = Gun.discriminator("M16", m16Schema );async.series(  [    // Cleanup    function(callback) {      async.each([Scenario,Gun],function(model,callback) {        model.remove({},callback);      },callback);    },    // Add some guns and add to scenario    function(callback) {      async.waterfall(        [          function(callback) {            async.map([Ak47,M16],function(gun,callback) {              gun.create({},callback);            },callback);          },          function(guns,callback) {            Scenario.create({              "name": "Test",              "guns": guns            },callback);          }        ],        callback      );    },    // Get populated scenario    function(callback) {      Scenario.findOne().populate("guns").exec(function(err,data) {        console.log("Populated:\n%s",JSON.stringify(data,undefined,2));        // Shoot each gun for fun!        data.guns.forEach(function(gun) {          console.log("%s says %s",gun.__t,gun.shoot());        });        callback(err);      });    },    // Show the Guns collection    function(callback) {      Gun.find().exec(function(err,guns) {        console.log("Guns:\n%s", JSON.stringify(guns,undefined,2));        callback(err);      });    },    // Show magic filtering    function(callback) {      Ak47.find().exec(function(err,ak47) {        console.log("Magic!:\n%s", JSON.stringify(ak47,undefined,2));        callback(err);      });    }  ],  function(err) {    if (err) throw err;    mongoose.disconnect();  });

And output

Populated:{  "_id": "56c508069d16fab84ead921d",  "name": "Test",  "__v": 0,  "guns": [    {      "_id": "56c508069d16fab84ead921b",      "__v": 0,      "__t": "Ak47",      "createdAt": "2016-02-17T23:53:42.853Z"    },    {      "_id": "56c508069d16fab84ead921c",      "__v": 0,      "__t": "M16",      "createdAt": "2016-02-17T23:53:42.862Z"    }  ]}Ak47 says Crack!CrackM16 says Blam!!Guns:[  {    "_id": "56c508069d16fab84ead921b",    "__v": 0,    "__t": "Ak47",    "createdAt": "2016-02-17T23:53:42.853Z"  },  {    "_id": "56c508069d16fab84ead921c",    "__v": 0,    "__t": "M16",    "createdAt": "2016-02-17T23:53:42.862Z"  }]Magic!:[  {    "_id": "56c508069d16fab84ead921b",    "__v": 0,    "__t": "Ak47",    "createdAt": "2016-02-17T23:53:42.853Z"  }]

You can also uncomment the mongoose.set("debug",true) line in the listing to see how mongoose is actually constructing the calls.

So what this demonstrates is that you can apply different schemas to different first class objects, and even with different methods attached to them just like real objects. Mongoose is storing these all in a "guns" collection with the attached model, and it will contain all "types" refernced by the discriminator:

var Gun = mongoose.model("Gun", gunSchema );var Ak47 = Gun.discriminator("Ak47", ak47Schema );var M16 = Gun.discriminator("M16", m16Schema );

But also each different "type" is referenced with it's own model in a special way. So you see that when mongoose stores and reads the object, there is a special __t field which tells it which "model" to apply, and hence attached schema.

As one example we call the .shoot() method, which is defined differently for each model/schema. And also you can still use each as a model by itself for queries or other operations, since Ak47 will automatically apply the __t value in all query/upates.

So though the storage is in one collection it can appear to be many collections, but also has the benefit of keeping them together for other useful operations. This is how you can apply the kind of "polymorphism" you are looking for.