MongoDB / Mongoose one-to-many relationship - mongodb

Working on a database structure for a new project, and trying to figure out how exactly one-to-many relationships in MongoDB / mongoose work.
So considering a following simple structure :
I have a project table/schema with multiple images linked to it:
const ProjectSchema = mongoose.Schema({
_id: mongoose.Types.ObjectId, // Just the id,
name: { type: String, required: true, trim: true, maxLength: 60 },
description: { type: String, required: false },
images: [{
type: Schema.Types.ObjectId, ref: 'Image'
}]
});
And then an image table/schema:
const ImageSchema = new Schema({
_id: mongoose.Types.ObjectId, // Just the id,
url: { type: String, unique: true, required: true }
project: {
type: Schema.Types.ObjectId, ref: 'Project'
}
});
I want one-to-many between images and projects, so I save an image with a project id:
const image = new Image(
{
project: project._id,
_id: new mongoose.Types.ObjectId(),
url: 'https://someurl',
});
await image.save();
If I then find this image, and populate the project field - it contains all the project's info nicely, BUT if I find this project, it doesn't have anything in the images array (referencing Image):
images: [{
type: Schema.Types.ObjectId, ref: 'Image'
}]
I thought that with ref you're creating the foreign key reference between Project and Image and then both Image should have the linked Project and Project should see the linked image in the array.
Is that not the case or am I missing something here ?

You should be able to populate images from project with no problem.
Let's say you have this project document:
{
"_id" : ObjectId("5e592a438b93764a40a81a96"),
"images" : [
ObjectId("5e592aba8b93764a40a81a98"),
ObjectId("5e592ac78b93764a40a81a99")
],
"name" : "Project 1",
"__v" : 0
}
And these image documents:
{
"_id" : ObjectId("5e592ac78b93764a40a81a99"),
"url" : "Url 2",
"project" : ObjectId("5e592a438b93764a40a81a96"),
"__v" : 0
},
{
"_id" : ObjectId("5e592aba8b93764a40a81a98"),
"url" : "Url 1",
"project" : ObjectId("5e592a438b93764a40a81a96"),
"__v" : 0
}
We can use the following code to populate images:
router.get("/projects/:id", async (req, res) => {
const result = await Project.findById(req.params.id).populate("images");
res.send(result);
});
This will give a result like this:
{
"images": [
{
"_id": "5e592aba8b93764a40a81a98",
"url": "Url 1",
"project": "5e592a438b93764a40a81a96",
"__v": 0
},
{
"_id": "5e592ac78b93764a40a81a99",
"url": "Url 2",
"project": "5e592a438b93764a40a81a96",
"__v": 0
}
],
"_id": "5e592a438b93764a40a81a96",
"name": "Project 1",
"__v": 0
}
So for your case, check if your project document really contains images array with the Object ids for image documents, and you populate correctly.
Also you don't need to add _id fields to the schema, mongodb will itself generate _id automatically.
project
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ProjectSchema = Schema({
name: { type: String, required: true, trim: true, maxLength: 60 },
description: { type: String, required: false },
images: [
{
type: Schema.Types.ObjectId,
ref: "Image"
}
]
});
module.exports = mongoose.model("Project", ProjectSchema);
image
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ImageSchema = new Schema({
url: { type: String, unique: true, required: true },
project: {
type: Schema.Types.ObjectId,
ref: "Project"
}
});
module.exports = mongoose.model("Image", ImageSchema);

Related

How can I populate properties of array item in mongoDB?

I have 3 collections scopes, groups and users
const ScopesSchema = new mongoose.Schema(
{
name: String,
privilege: String,
}
);
const GroupsSchema = new mongoose.Schema(
{
name: String,
status: String,
}
);
const UserSchema = new mongoose.Schema(
{
security:[{
_id:false,
group: { type: mongoose.Schema.Types.ObjectId, ref: "groups" },
scopes: [{ type: mongoose.Schema.Types.ObjectId, ref: "scopes" }],
}],
},
);
Is possible to populate data from properties group and scopes in a document like this?
{
_id: 44ffbvb...,
security: [{
"group": "44ffbvb...", // ID of document in groups collection
"scopes": ["44ffbvb...","44ffbvb..."] // IDs of documents in scopes collection
}]
}
I'd like to get the information related to group and scopes in order to get a document like this:
{
_id: 44ffbvb...,
security: [{
"group": {
"id" : "44ffbvb...",
"name" : "Name of the group",
"status": "ACTIVE"
},
"scopes": [{name: "ADMINISTRATOR", privilege: "write-only" },{name: "ROOT", privilege: "read-only" }]
}]
}
you can use the npm package
mongoose-autopopulate
The fields you need to be populated, add : autopopulate: true,
and add this line to the schema-
schema.plugin(require('mongoose-autopopulate'));

Create ref to sub document's array for each property of subdocument

Model A looks like
{ a : {
step1: [{
type: { type: String },
category: { type: String },
}],
step2: [{
type: { type: String },
category: { type: String },
}]
} }
Model B which I wanted to create should contain a prop which will ref to Model A.step1 or A.step2 , trying to acheive this by following
{ progress : [
{
step: {
type: Schema.Types.ObjectId,
ref: "A.a.{What should be here}",
required: true,
},
details: { type: Schema.Types.Mixed },
}
]
}
I think you need to create Schemas for all of them :)
I would just separate the three completely - not sure if this is viable to you as the whole idea behind this is a bit mysterious, but would something like this work for you?
const stepSchema = new Schema({
type: String,
category: String,
});
const Step = mongoose.model("steps", stepSchema);
const aSchema = new Schema({
step1: [stepSchema],
step2: [stepSchema],
});
const A = mongoose.model("as", aSchema);
const progressSchema = new Schema({
a: { type: aSchema, required: true, ref: "as"},
progress: [{ type: stepSchema, required: true, ref: "steps" }],
details: Schema.Types.Mixed,
});
const Progress = mongoose.model("progresses", aSchema);

Populating and selecting multiple sub-documents mongoose

I have a User Model
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
}
email: {
type: String,
required: true,
maxlength: 128,
minlength: 5
},
hashedPassword: {
type: String,
required: false
}
});
module.exports = mongoose.model('users', UserSchema);
And a Post Model
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
description: {
type: String,
required: true
},
comments: [{
comment: {
type: String,
required: true
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'users'
},
postedOn: {
type: Date,
required: true,
default: Date.now
}
}],
postedBy: {
type: mongoose.Types.ObjectId,
required: true,
ref: 'users'
}
});
module.exports = mongoose.model('answers', AnswerSchema);
I want to fetch post with populated "postedBy"(of POST) also select fields of "name and email" from "postedBy"(of POST). Also, I want to populate "postedBy"(inside comments) and select the same field of "name and email" from "postedBy"(inside comments).
Expecting to see a result like
{
"post": [
{
"_id": "*some mongo id*",
"description": "This is a sample Post for testing",
"postedBy": {
"_id": "5e9285669bdcb146c411cef2",
"name": "Rohit Sharma",
"email": "rohit#gmail.com"
},
"comments": [{
"comment": "Test One Comment",
"postedBy": {
"_id": "5e9285669bdcb146c411cef2",
"name": "Rohit Sharma",
"email": "rohit#gmail.com"
},
}],
"postedOn": "2020-04-19T12:28:31.162Z"
}
]
}

Return the actual document instead of ObjectId

So, I have a model called Drivers that receive a field called "user", which references a document from another model, like this:
const DriversSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
},
{
timestamps: true,
}
);
// ...
Querying the collection works as expected, here's an example:
Drivers.find({});
// returns ->
[
{
"name": "John",
"user": "5e43f8ad2fbb1d0035d5a154",
}
]
Is there a way to return the actual document represented by the 'user' field?
Thanks!

Mongoose query not showing subdocument

I can't get mongoose to show subdocument when running find() while it displays perfectly well in mongodb shell.
Subdocument should be embedded based on my schema, not objectId referenced, so I shouldn't be running any black magic voodoo to get my data to show up.
const UserSchema = new mongoose.Schema({
username: String;
xp: Number;
//etc.
});
const RoomSchema = new mongoose.Schema({
timestamp: { type: Date, default: Date.now },
status: { type: String, enum: ["pending", "ongoing", "completed"]},
players: {
type: [{
points: { type: Number, default: 0 },
position: String,
user: UserSchema
}],
maxlength:2
}
});
After adding a new room with:
let room = new Room(coreObj);
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
It displays nicely in mongo shell, when running db.rooms.find({}).pretty() I can see that full document has been added. However, when running on mongoose model:
Room.find({}).exec((err,rooms)=>{
console.log(rooms[0].toJSON());
});
I don't see user subdocument, moreover I cannot see user field entirely! What seems to be the problem?
logged json from mongoose model:
{
"status": "pending",
"_id": "5cf5a25c050db208641a2076",
"timestamp": "2019-06-03T22:42:36.946Z",
"players": [
{
"points": 0,
"_id": "5cf5a25c050db208641a2077",
"position": "blue"
}
],
"__v": 0
}
json from mongo shell:
{
"_id" : ObjectId("5cf5a25c050db208641a2076"),
"status" : "pending",
"timestamp" : ISODate("2019-06-03T22:42:36.946Z"),
"players" : [
{
"points" : 0,
"_id" : ObjectId("5cf5a25c050db208641a2077"),
"position" : "blue",
"user" : {
"xp" : 0,
"_id" : ObjectId("5cf2da91a45db837b8061270"),
"username" : "bogdan_zvonko",
"__v" : 0
}
}
],
"__v" : 0
}
Keeping best practice in mind, I think it would be more appropriate to reference the UserSchema in the RoomSchema. Something like:
...
user: {
type: Schema.Types.ObjectId,
ref: 'UserSchema'
}
Then you would store the user._id in that field.
This way, if the user is modified, your RoomSchema is always referencing the correct information. You could then get the user using Mongoose's populate
I'm not entirely sure why you can't see the sub-sub-document, but this code example printed it correctly for me. Example was originally posted in https://mongoosejs.com/docs/subdocs.html but modified slightly to contain sub-sub-document so it looks similar to your code:
var grandChildSchema = new mongoose.Schema({ name: 'string' });
var childSchema = new mongoose.Schema({ name: 'string', grandChild: grandChildSchema });
var parentSchema = new mongoose.Schema({ children: [childSchema] });
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({
children: [
{ name: 'Matt', grandChild: {name: 'Matt Jr'} },
{ name: 'Sarah', grandChild: {name: 'Sarah Jr'} }
]
})
parent.save(function() {
Parent.find().exec(function(err, res) {
console.log(JSON.stringify(res[0]))
mongoose.connection.close()
})
});
Executing this code resulted in:
{
"_id": "5cf7096408b1f54151ef907c",
"children": [
{
"_id": "5cf7096408b1f54151ef907f",
"name": "Matt",
"grandChild": {
"_id": "5cf7096408b1f54151ef9080",
"name": "Matt Jr"
}
},
{
"_id": "5cf7096408b1f54151ef907d",
"name": "Sarah",
"grandChild": {
"_id": "5cf7096408b1f54151ef907e",
"name": "Sarah Jr"
}
}
],
"__v": 0
}
This was tested using Mongoose 5.5.12.
Note that I was using JSON.stringify() to print the document instead of using Mongoose's toJSON().
I just met a very similar problem, i think i got it.
the whole point is in model which you use:
const RoomSchema = new mongoose.Schema({
...
players: {
type: [{
...
user: UserSchema
...
but then you make
room.players.push({
points: 0,
position: 'blue',
user: userObj //where userObj is a result of running findById on User model
});
so you are missing the "type" subfield, so your doc is not compliant with your RoomSchema and mongoose do not show the parts which does not fit schema.