Combining multiple schema references within a single schema array using Mongoose

Is it possible to populate an array within a mongoose schema with references to different schema options?

To clarify, let's say we have the following schemas:

var scenarioSchema = Schema({
  _id     : Number,
  name    : String,
  guns : []
});

var ak47 = Schema({
  _id     : Number
  //AK specific parameters
});

var m16 = Schema({
  _id     : Number
  //M16 specific parameters
});

Can the guns array contain both instances of ak47 and m16? Is it possible to have both types in the same guns array, or does each gun need a specific reference in the assets array like this example that limits it to only one type?

guns: [{ type: Schema.Types.ObjectId, ref: 'm16' }]

I understand that having separate arrays for different gun types can lead to excessive fields in the schema as the project grows, many of which may be empty depending on the scenario being loaded.

var scenarioSchema = Schema({
  _id     : Number,
  name    : String,
  ak47s : [{ type: Schema.Types.ObjectId, ref: 'ak47' }],
  m16s: [{ type: Schema.Types.ObjectId, ref: 'm16' }]
});

So, the question remains, can multiple schema references be included in a single array?

Answer №1

If you're searching for a way to store objects of different types in the same collection while maintaining them as distinguishable first class objects, the mongoose .discriminator() method is what you need.

It's crucial to note that the concept of the "same collection" plays a significant role in how the .populate() function operates and how the reference is defined in the parent model. While you can only point to one model for a reference, there are mechanisms in place to make it seem like there are multiple models at play.

For instance, using the example below:

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 );

// Remaining code for adding guns to scenarios, populating, querying, etc.

This setup allows you to have different schemas for various first-class objects, including unique methods attached to each model. Despite all data being stored in a single "guns" collection with its corresponding model, mongoose uses a special "__t" field to differentiate between the types referenced by the discriminator.

Methods specific to each schema, such as .shoot(), are applied accordingly when interacting with the objects. Additionally, individual models like Ak47 can be utilized for queries or updates, automatically applying the "__t" value for differentiation.

In essence, although the storage appears as one collection, the implementation mimics multiple collections, offering the advantages of grouped operations while supporting polymorphism effectively.

Answer №2

Here is my proposed solution for this particular issue, utilizing the concept of utilizing discriminators

  • baseSchema: Primary model collection that contains an array field with multiple schemas
  • itemSchema: Parent schema for discriminator usage
  • fizzSchema & buzzSchema: Multiple schemas intended to be used in an array

Model

const itemSchema = Schema({
  foo: String,
}, { discriminatorKey: 'kind', _id: false});

const fizzSchema = Schema({
  fizz: String,
}, { _id: false });

const buzzSchema = Schema({
  buzz: String,
}, { _id: false });

const baseSchema = Schema({
  items: [itemSchema],
});

baseSchema.path('items').discriminator('fizz', fizzSchema);
baseSchema.path('items').discriminator('buzz', buzzSchema);

const List = model('list', baseSchema);

Testbench

const body = {
  items: [
    { foo: 'foo'},
    { foo: 'foo', fizz: 'fizz'},
    { foo: 'foo', kind: 'fizz', fizz: 'fizz'},
    { foo: 'foo', kind: 'buzz', buzz: 'buzz'},
    { kind: 'buzz', buzz: 'buzz'},
  ]
};

const doc = new List(body);

console.log(doc);

Output

{
  items: [
    { foo: 'foo' },
    { foo: 'foo' },
    { fizz: 'fizz', foo: 'foo', kind: 'fizz' },
    { buzz: 'buzz', foo: 'foo', kind: 'buzz' },
    { buzz: 'buzz', kind: 'buzz' }
  ],
  _id: new ObjectId("626a7b1cf2aa28008d2be5ca")
}

You can run this example here:

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering a 404 error while sending a session ID in a Post request using AngularJS

My services are hosted on a remote server, and I am consuming these services in a local AngularJS app. Everything works fine with REST requests that do not require a SessionID in the header. However, when I add the Session ID in the header, it does not wor ...

I'm having trouble getting my application to output to the console. What could be the issue?

In my cr-route.js file, I have a function that ensures the user is authenticated before displaying their name. module.exports = function (app, passport) { // ===================================== // HOME PAGE (with login links) ======== // == ...

In the JavaScript example provided, do child classes inherit their parent class prototype?

Here's the code I'm working with: class Rectangle { constructor(w, h) { this.w = w; this.h = h; } } Rectangle.prototype.area = function () { return (this.w * this.h); }; class Square extends Rectangle { construct ...

Steps for sending a POST request for every file in the given array

I am working on an angular component that contains an array of drag'n'dropped files. My goal is to make a POST request to http://some.url for each file in the array. Here is what I have been attempting: drop.component.ts public drop(event) { ...

What is the best approach to establish the right 'this' Lexical Scope while utilizing Promises in ES6/React?

I've been exploring the use of the FourSquare API to retrieve location data within my React App. To achieve this, I'm leveraging a convenient package available here: https://www.npmjs.com/package/foursquarevenues The package employs promises to ...

Uncheck the previous option selected in a multi-select dropdown using jQuery

Hey there, I've been facing an issue with my change handler when trying to add or remove values from an Array. It works fine until the last element is selected, at which point the change event does not fire properly. Has anyone else encountered this p ...

Using Javascript to prevent css from changing the display

I want to make some CSS adjustments to a single element using a single script, but I want all the changes to be displayed at once. For example: $("#someelement").css({width:"100%"}); //this change should not be displayed $("#someelement").width($("#someel ...

How can I trigger a function by clicking on a link that was generated dynamically with jQuery?

I am dynamically creating multiple <a href="#"></a> elements with dynamically assigned id attributes like <a href="#" id='delete"+id+"'></a>. The IDs generated will be like delete01, delete02, .... I want to trigger a func ...

Utilizing Node.js with Mongoose callbacks

Despite reading numerous answers, I am still struggling to get this function to work. My goal is to return the length of a query find on Mongoose with a simple function structure as follows: app.use(function(req, res, next) { res.locals.user = nul ...

Exploring Angular2 components featuring setInterval or setTimeout functions

I have a basic ng2 component that interacts with a service to retrieve carousel items and automatically cycle through them using setInterval. Everything functions properly, but I encounter the error "Cannot use setInterval from within an async test zone" w ...

Encountering an issue with trying to filter a hasMany relationship where the property 'collectionName' is undefined

In my project, I am utilizing adonis.js/lucid ^6.1.3 along with lucid-mongo ^3.1.5 library for managing a MongoDB database. As part of my development process, I encountered an issue while trying to build a query and filter a hasMany relationship resulting ...

Unleash the power of jQuery to leverage two different input methods

Here is the code I'm working with: $('input[type="text"]').each(function(indx){ Right now, this code targets input elements with type "text". But how can I modify it to target both inputs with type "text" and "password"? Any suggestions o ...

Is there a way to alter the color of a button once it has been clicked?

For a school project, I am working on coding a website to earn extra credit. My goal is to create a fancy design that stands out. One of the main things I am trying to achieve is having a button change color when it is clicked, similar to how it highlight ...

Learn about Angular8's prototype inheritance when working with the Date object

In my search for a way to extend the Date prototype in Angular (Typescript), I stumbled upon a solution on GitHub that has proven to be effective. date.extensions.ts // DATE EXTENSIONS // ================ declare global { interface Date { addDa ...

Is there a way for me to set distinct values for the input box using my color picker?

I have two different input boxes with unique ids and two different color picker palettes. My goal is to allow the user to select a color from each palette and have that color display in the corresponding input box. Currently, this functionality is partiall ...

Utilizing Ajax, Jquery, and Struts2 for File Uploading

Can someone please provide guidance on uploading files using a combination of Ajax, jQuery, and Struts2? I have searched through numerous tutorials online but haven't found a suitable solution yet. The goal is to trigger a JavaScript function when a b ...

Creating a response in Node JS

I'm struggling with crafting a response to the server. Currently, I have a login form that sends data to the server. On the backend, I verify if this data matches the information in a .json file and aim to send a response upon successful validation. ...

Is there a way to utilize a parameter for the user's input instead of relying on document.getElementById when incorporating a button?

let totalScore = 0; var myArray = [1, 2, 3, 4, 5]; let randomNum; function NumGuess(userInput) { var userGuess = userInput; var i; for (i = 0; i < 1; i++) { var randomNum = myArray[Math.floor(Math.random() * myArray.length)]; } if (us ...

Creating a Three by Three Grid with HTML and CSS

I have a total of 20 elements to display in a grid view. However, I specifically want a 3x3 grid view, where only 9 elements will be visible in the view window. The remaining elements should be placed on the right side of the window in a scrollable manner. ...

What is the method for retrieving a property from an object contained within an array that is assigned to a property of another object?

How can I retrieve the name property from the subjects array within a course object? The database in use is mongodb. Modifying the course model is not an option. The course model : const mongoose = require('mongoose'); const Schema = mongoose. ...