Structure for deep nests in mongoose - node.js

Mongo noob here. I'm building a mongoose schema with a lot of one-to-many relationships, and this seems to be a good opportunity to use nesting. I have a collection "countries", each has many "states", each has many "cities", each has many "people." I'm going to want to run all different sorts of queries...get states by country, insert new city into state, remove a person from a city, etc.
How do I structure it? My current schema looks like this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
;
var countrySchema = Schema({
name: { type: String },
states: [ {
name: { type: String },
cities: [ {
name: { type: String },
people: [ {
name: { type: String }
} ]
} ]
} ]
});
To me, that makes sense. I will likely be pulling entities based on deep nests, so this kind of nested schema should give me the performance edge that a heavily-joined relational database won't (right?).
It is, however, very ugly. I have also seen this style of schema construction:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
;
var countrySchema = mongoose.schema({
name: { type: String },
states: [ { type: ObjectId, ref: State } ]
});
var stateSchema = mongoose.schema({
name: { type: String },
cities: [ { type: ObjectId, ref: City } ]
});
var citySchema = mongoose.schema({
name: { type: String },
persons: [ { type: ObjectId, ref: Person } ]
});
var personSchema = mongoose.schema({
name: { type: String },
});
var Country = mongoose.model('Country', countrySchema)
, State = mongoose.model('State', stateSchema)
, City = mongoose.model('City', citySchema)
, Person = mongoose.model('Person', personSchema)
;
Is this a better way to do it? It seems more readable, but I imagine it doesn't perform as well.

Related

Mongoose - Find document's child array by a specific field

I had spent hours trying to work out how to get records from a document's child array by a specific field, but I failed it.
I would like to pass a personId by a web service to find which meeting he/she has been invited to. As a result, I could track down whether the invitee has accept to join the meeting or not.
Basically, I have the following JSON output:
{
"status": "success",
"requestedAt": "2021-03-28T22:47:03+11:00",
"size": 1,
"meetings": [
{
"invitedMembers": [
{
"isJoined": false,
"_id": "605ffbc00a21ed718c992549",
"person": "a123",
"__v": 0
}
]
}
]
}
with a controller like this:
const memberId = "a123";
const meetings = await Meeting.find({
'invitedMembers.member': memberId
}).populate('invitedMembers');
a meeting model class like below:
const mongoose = require('mongoose');
const meetingSchema = new mongoose.Schema({
invitedMembers: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'InvitedMembers'
}
]
});
const Meeting = mongoose.model(
'Meeting',
meetingSchema
);
module.exports = Meeting;
and a invitedMembers class like this:
const mongoose = require('mongoose');
const invitedMembersSchmea = new mongoose.Schema({
member: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Member',
required: true
},
isJoined: {
type: Boolean,
default: false
}
});
const InvitedMembers = mongoose.model(
'InvitedMembers',
invitedMembersSchmea
);
module.exports = InvitedMembers;
The Member schema only contains a basic personal information such as first name, last name and etc.
I ended up solving my own problem by using a different approach where I changed my data structure by adding invitedMembers as an embedding model in the meeting model and updated the person field in the invitedMembers schema to _id.
Updated Meeting model class:
const mongoose = require('mongoose');
const invitedMembersSchmea = new mongoose.Schema({
_id: {
type: String,
required: true
},
isJoined: {
type: Boolean,
default: false
}
});
const meetingSchema = new mongoose.Schema({
invitedMembers: [
{
type: invitedMembersSchmea
}
]
});
const Meeting = mongoose.model(
'Meeting',
meetingSchema
);
module.exports = Meeting;
As a result, I can find the invited member by ID using the following query:
const memberId = "a123";
const meetings = await Meeting.find({
'invitedMembers._id': memberId
});

How to put collection name in mongoose model

mY model,
var CategorySchema = new Schema({
categoryname: {
type: String
},
topictitle: {
type: String
},
topicdescription: {
type: String
},
tag: {
type: String
},
topicid: {
type: Schema.ObjectId
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('topics', CategorySchema);
Here topics is the name of my model but where can i keep my collection name?Can anyone suggest me help.
In the below model definition
mongoose.model('topics', CategorySchema);
var topics = mongoose.model('topics', CategorySchema,'topics' );
var tags = mongoose.model('tags', TagSchema, 'topics');
'topics' (last parameter) is the collection name & CategorySchema is the structure of a particular document
Check Mongoose documentation

Mongoose Refs to children

I am struggling to find examples or documentation on mongoose children population.
http://mongoosejs.com/docs/populate.html
On the documentation they have:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
It makes sense, a Person can have many stories thus the 'parent' field 'stories' which has a list of all the stories in.
What I am struggling to understand is how do you push the the story into the person schema.
For example, i have an assignment schema:
var mongoose = require ( 'mongoose' ),
Schema = mongoose.Schema;
var assignmentSchema = new Schema (
{
_id: String,
assignName: String,
modInsID: [{ type: Schema.Types.Number, ref: 'ModuleInst' }],
studentAssigns: [{type: Schema.Types.ObjectId, ref: 'StudentAssign' }]
}
);
module.exports = mongoose.model ( 'Assignment', assignmentSchema );
The studentAssigns stores all the id's of the studentAssigns which then can be used with the .pre middleware for cascade deleting.
So now my StudentAssign schema:
var mongoose = require ( 'mongoose' ),
autoIncrement = require ( 'mongoose-auto-increment' ),
Schema = mongoose.Schema;
var connection = mongoose.createConnection("************");
autoIncrement.initialize(connection);
var studentAssignSchema = new Schema (
{
assID: [{ type: Schema.Types.String, ref: 'Assignment' }],
studentID: [{ type: Schema.Types.Number, ref: 'Student' }]
}
);
var StudentAssign = connection.model('StudentAssign', studentAssignSchema);
module.exports = mongoose.model ('StudentAssign', studentAssignSchema );
As you can see it already is referencing 'Assignment'
Here is my api code:
studentAssign POST:
router.route('/student-assignment').post( function(req, res) {
var studentAssign = new StudentAssign();
studentAssign.assID = req.body.assID;
studentAssign.studentID = req.body.studentID;
studentAssign.save(function(err, studentAssign) {
if(err) console.log(err);
res.status(200).json(studentAssign);
});
})
So that's the part I am confused at where would I push the 'studentAssign' into 'Assignment' schema's 'studentAssigns array ??
here is my current api json callback:
[
{
"_id": "As 1",
"assignName": "Software Implementation",
"__v": 0,
"studentAssigns": [],
"modInsID": [
{
"_id": 22,
"semester": "TRI 3",
"year": 2016,
"__v": 0,
"modID": [
111
]
}
]
}
]
The documentation just does not make it clear as they just show:
aaron.stories.push(story1);
aaron.save(callback);
With no explanation?
I have attempted:
var assignment = new Assignment();
assignment.studentAssigns.push(studentAssign); and nothing gets stored ??
Here is a working example based on the documentation docs
const mongoose = require('mongoose');
const { Schema } = mongoose;
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
Person model has its stories field set to an array of ObjectId's
For saving Refs to children you may have first to save stories before pushing them to Person's stories field
const story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
story1.save();
And before pushing story1 find the author id you want to push to
const author = await Person.findOne({_id: "624313f302e268b597b8df1f"})
if(Array.isArray(author.stories)) author.stories.push(story1);
author.save()
You model states :
assID: [{ type: Schema.Types.String, ref: 'Assignment' }],
studentID: [{ type: Schema.Types.Number, ref: 'Student' }]
I think from your code you don't want to store multiple assignments in assID and multiple Students in studentID. Modify your model to
assID: { type: Schema.Types.String, ref: 'Assignment' },
studentID: { type: Schema.Types.Number, ref: 'Student' }
Your save code can stay the same.
If you do want to store for example multiple assignments, you need to push them into the assID array ;-)
router.get('/author', (req, res, next) => {
Person.
find().
exec( (err, person) => {
if (err) return handleError(err)
Story.find({author: person[0]._id}).
exec( (err, story) => {
if (err) return handleError(err)
person[0].stories.push(story[0])
res.json( { person: person })
})
})
})

Nest mongoose schema within itself?

Is it possible with mongoose to create a schema, call it Folders and it has a property within called subfolders that is an array of nested Folder subdocs?
const mongoose = require('mongoose')
let SubSchema = mongoose.Schema({
name: { type: String, required: true }
})
let FolderSchema = mongoose.Schema({
name: { type: String, required: true },
children: [ SubSchema ]
})
I know I can nest subdocs in an array by referencing another schema similar to what is shown above. What I'm looking to do though is reuse FolderSchema within itself. Obviously this causes an issue because at the time of creation of the schema, FolderSchema doesn't exist. Is there a better way to do this? Is there a way to recursively nest documents using the same schema?
I know I could have the array be a list of ObjectId that reference a collection but I was hoping to just keep it all nested as documents. I guess if I did populate() to let it resolve the doc ids, that would essentially be the same thing. Just wondering if there is another method I wasn't aware of.
I haven't tried this personally, but I have read this can be achieved by simply referencing this in the field you want to reference the current model.
The model you would like to will look something like this,
const mongoose = require('mongoose')
const FolderSchema = mongoose.Schema({
name: { type: String, required: true },
type: { type: String, enum: ['file', 'directory'],
children: [ this ]
})
const FolderModel = mongoose.model('Folder', FolderSchema);
Hope that helps!
look you need to clarify your question a little bit but as much as i understood from the question, yes it can be done in this way :
var mongoose = require('mongoose');
var FolderSchema = new mongoose.Schema({
SubFolders = [ type:monogoose.Schema.Types.ObjectId, ref : 'Folders']
});
var folder = mongoose.model('Folders',FolderSchema);
module.exports = folder;
This shall work for you.
So for infinite object having children, I did it like so:
mongoose schema:
const itemSchema = new mongoose.Schema({
name: String,
items: {
type: [this],
default: undefined
}
}, { _id: false })
const mainSchema = new mongoose.Schema({
name: String,
items: {
type: [itemSchema],
default: undefined
}
})
output example:
[
{
_id: '62a72d6915ad7f79d738e465',
name: 'item1',
items: [
{
name: 'item1-item1',
items: [
{
name: 'item1-item1-item1'
},
{
name: 'item1-item1-item2'
},
{
name: 'item1-item1-item3'
},
]
},
{
name: 'item1-item2'
}
]
},
{
_id: '62a72d6915ad7f79d738e467',
name: 'item2'
},
{
_id: '62a72d6915ad7f79d738e467',
name: 'item3',
items: [
{
name: 'item3-item1'
}
]
}
]

Many-to-many mapping with Mongoose

I have FlashcardSchemas and PackageSchemas in my design. One flashcard can belong to different packages and a package can contain different flashcards.
Below you can see a stripped down version of my mongoose schema definitions:
// package-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var PackageSchema = new Schema({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
var exports = module.exports = mongoose.model('Package', PackageSchema);
// flashcard-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
var exports = module.exports = mongoose.model('Flashcard', FlashcardSchema);
As you can see from the comments above, these two schema definitions belong to separate files and reference each other.
I get an exception stating that PackageSchema is not defined, as expected. How can I map a many-to-many relation with mongoose?
I am new to node, mongoDB, and mongoose, but I think the proper way to do this is:
var PackageSchema = new Schema({
id: ObjectId,
title: { type: String, required: true },
flashcards: [ {type : mongoose.Schema.ObjectId, ref : 'Flashcard'} ]
});
var FlashcardSchema = new Schema({
id: ObjectId,
type: { type: String, default: '' },
story: { type: String, default: '' },
packages: [ {type : mongoose.Schema.ObjectId, ref : 'Package'} ]
});
This way, you only store the object reference and not an embedded object.
You are doing it the right way, however the problem is that you have to include PackageSchema in the the flashcard-schema.js, and vice-versa. Otherwise these files have no idea what you are referencing
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
PackageSchema = require('./path/to/package-schema.js')
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
You could use the Schema.add() method to avoid the forward referencing problem.
This (untested) solution puts the schema in one .js file
models/index.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
// avoid forward referencing
var PackageSchema = new Schema();
var FlashcardSchema = new Schema();
PackageSchema.add({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
FlashcardSchema.add({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
// Exports both types
module.exports = {
Package: mongoose.model('Package', PackageSchema),
Flashcard: mongoose.model('Flashcard', FlashcardSchema)
};
You're thinking of this too much like a relational data store. If that's what you want, use MySQL (or another RDBMS)
Failing that, then yes, a third schema could be used, but don't forget it'll still only be the id of each object (no joins, remember) so you'll still have to retrieve each other item in a separate query.
https://www.npmjs.com/package/mongoose-relationship
##Many-To-Many with Multiple paths
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
relationship = require("mongoose-relationship");
var ParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var Parent = mongoose.models("Parent", ParentSchema);
var OtherParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var OtherParent = mongoose.models("OtherParent", OtherParentSchema);
var ChildSchema = new Schema({
parents: [{ type:Schema.ObjectId, ref:"Parent", childPath:"children" }]
otherParents: [{ type:Schema.ObjectId, ref:"OtherParent", childPath:"children" }]
});
ChildSchema.plugin(relationship, { relationshipPathName:['parents', 'otherParents'] });
var Child = mongoose.models("Child", ChildSchema)
var parent = new Parent({});
parent.save();
var otherParent = new OtherParent({});
otherParent.save();
var child = new Child({});
child.parents.push(parent);
child.otherParents.push(otherParent);
child.save() //both parent and otherParent children property will now contain the child's id
child.remove()
This is the problem of cyclic/circular dependency. This is how you make it work in nodejs. For more detail, check out "Cyclic dependencies in CommonJS" at http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript
//------ a.js ------
var b = require('b');
function foo() {
b.bar();
}
exports.foo = foo;
//------ b.js ------
var a = require('a'); // (i)
function bar() {
if (Math.random()) {
a.foo(); // (ii)
}
}
exports.bar = bar;

Resources