O que você está procurando aqui é o mangusto
.discriminator()
método. Isso basicamente permite armazenar objetos de diferentes tipos na mesma coleção, mas tê-los como objetos de primeira classe distinguíveis. Observe que o princípio da "mesma coleção" aqui é importante para como
.populate()
funciona e a definição da referência no modelo que o contém. Desde que você realmente só pode apontar para "um" modelo para referência de qualquer maneira, mas há alguma outra mágica que pode fazer com que um modelo apareça como muitos. Exemplo de listagem:
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();
}
);
E saída
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!Crack
M16 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"
}
]
Você também pode descomentar o
mongoose.set("debug",true)
linha na listagem para ver como o mangusto está realmente construindo as chamadas. Então, o que isso demonstra é que você pode aplicar diferentes esquemas a diferentes objetos de primeira classe e até mesmo com diferentes métodos anexados a eles como objetos reais. O Mongoose está armazenando tudo isso em uma coleção de "armas" com o modelo anexado e conterá todos os "tipos" referenciados pelo discriminador:
var Gun = mongoose.model("Gun", gunSchema );
var Ak47 = Gun.discriminator("Ak47", ak47Schema );
var M16 = Gun.discriminator("M16", m16Schema );
Mas também cada "tipo" diferente é referenciado com seu próprio modelo de uma maneira especial. Então você vê que quando o mangusto armazena e lê o objeto, há um
__t
especial campo que informa qual "modelo" aplicar e, portanto, o esquema anexado. Como exemplo, chamamos o
.shoot()
método, que é definido de forma diferente para cada modelo/esquema. E também você ainda pode usar cada um como um modelo por si só para consultas ou outras operações, pois Ak47
aplicará automaticamente o __t
valor em todas as consultas/atualizações. Portanto, embora o armazenamento esteja em uma coleção, pode parecer que são muitas coleções, mas também tem o benefício de mantê-las juntas para outras operações úteis. É assim que você pode aplicar o tipo de "polimorfismo" que está procurando.