Mongoose populate subdocument in array - javascript

I have a Mongoose offer model explained below:
const OfferSchema = new Schema({
sections: [
{
title: String,
},
],
});
and order schema which has reference to to the first schema offer explained below:
const OrderSchema = new Schema({
offers: [
{
offer: { type: Schema.Types.ObjectId, ref: 'Offer' },
sections: [
{
section: { type: Schema.Types.ObjectId, ref: 'Offer.sections' }, // issue here
},
],
},
],
});
the problem that I can not populate sections here {section: { type: Schema.Types.ObjectId, ref: 'Offer.sections' }}
it gives me MissingSchemaError: Schema hasn't been registered for model "Offer.sections".
so is there any way to populate sections?

Unfortunately, Mongoose doesn't support this feature.
check the Github issue here
The alternative solution you can embed sections into the order schema

Related

Mongoose - Populating a nested array of objects not working

I have a collection called Orders that contains this schema:
const mongoose = require('mongoose');
const orderSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
restaurant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Restaurant',
required: true
},
dishes: [
{
dish: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Dish'
},
amount: Number
}
],
price: {
type: Number,
required: true
},
comment: {
type: String,
required: false
},
status: {
type: String,
enum: ['PROCESSING', 'CANCELLED', 'COMPLETED', 'ERROR'],
default: 'PROCESSING'
},
timestamp: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Order', orderSchema);
Inside my router, I have this code:
let orders = await Order.find({restaurant: restaurantID, status:'PROCESSING'}).populate('dishes._id').exec()
Order.find does not throw an exception, but it isnt working either.
I want the res.body to look like this:
{
"_id": "objectID",
"user": "objectID",
"restaurant": "objectID",
"dishes": [
{
"amount": number,
"dish": {
//dish object
}
},
...
],
//other order properties
},
...
]
But for some reason the dishes array looks like this:
"dishes": [
{
"amount": 1,
"_id": "6184e848e6d1974a0569783d"
}
],
What am I doing wrong?
I know that if populate() worked the res.body dishes array would not have a property called 'dish' and instead have a property called _id that would contain the dish object, but this shouldnt be hard to change once populate() works.
EDIT:
I realised that my createOrder route could be part of the problem since it ignores my schema and uses an id property for the objectID instead of dish. The array I save to the DB contains a property called id for the id instead of dish, but shouldnt my schema throw an exception when i try to save something like this to my database?
At first glance, I think the problem might be that you have a syntax problem.
Try
.populate('dishes').exec()
instead of
.populate('dishes._id').exec()

Mongoose populate referencing object id, not the object itself

Background
Here's part of my User model:
const Group = require("./Group")
...
groups: {
type: [{ type: Schema.ObjectId, ref: Group }],
default: [],
},
And here's my Group model:
module.exports = mongoose.model(
"Group",
new Schema(
{
name: {
type: String,
required: true,
unique: true,
},
/**
* Array of User ObjectIDs that have owner rights on this group
*/
owners: {
type: [{ type: Schema.ObjectId, ref: User }],
default: [],
},
},
{
timestamps: true,
}
)
)
The Code
Here's the code I'm running to try and populate:
const user = await (await User.findOne({ _id: ... })).execPopulate("Group")
console.log(user.groups)
My console.log is outputting an array of object IDs, when I'd like it to output an actual Group document.
Attempted solutions
I've tried changing my ref to be using the string ("Group"), I've tried arranging my query differently, etc. I'm not sure how I'd go about doing this.
Apologies in advance if this is a duplicate, I've done my best to search but can't really find a solution that works for me.
Specifically, what do I need help with?
I'm trying to create a 'link' between a user model and a group model. In my console.log, I expect it to output a Group document; but it outputs an object ID (which is how it's stored raw in the database, meaning that Mongoose isn't transforming it correctly)
When you change execPopulate to populate like:
async function findUserAndPopulate(userId){
const response = await User.findOne({
_id: userId,
}).populate('groups')
console.log("response",response)
}
You got:
{
groups: [
{
owners: [Array],
_id: 5ecc637916a2223f15581ec7,
name: 'Crazy',
createdAt: 2020-05-26T00:31:53.379Z,
updatedAt: 2020-05-26T00:31:53.379Z,
__v: 0
}
],
_id: 5ecc6206820d583b99b6b595,
fullname: 'James R',
createdAt: 2020-05-26T00:25:42.948Z,
updatedAt: 2020-05-26T00:36:12.186Z,
__v: 1
}
So you can access the user.groups
See the doc: https://mongoosejs.com/docs/populate.html

Mongoose/Mongodb basic trello like scheme problem with rendering in vue

I'm creating a very basic functionality kanban board.
My board has 4 models so far:
User model
var userSchema = new Schema({
name: {
type: String,
required: true
}
})
module.exports = mongoose.model('User', userSchema)
Board model
var boardSchema = new Schema({
title: {
type: String,
required: true
},
lists: [ listSchema ]
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
});
module.exports = mongoose.model('Board', boardSchema)
List schema
let listSchema = new Schema({
title: {
type: String,
required: true
},
userCreated: {
type: Schema.Types.ObjectId,
required: true,
ref: 'user'
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('List', listSchema)
Card schema
var cardSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
listId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'list'
},
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('Card', cardSchema)
What am I looking for?
My front-end is made with Vue.js and sortable.js drag and drop lib.
I want to find the best way to render board with lists (columns) and cards in them.
From what I understand, I should get my board first, by the users id in members array.
Then I have my board which has lists embedded already.
On second api request, I get all the cards by boardId.
My question is - how do I correctly put/render all the cards into each owns lists?
So in the end I want to have something like:
{
title: 'My board',
lists: [
{
title: 'My list',
_id: '35jj3j532jj'
cards: [
{
title: 'my card',
listId: '35jj3j532jj'
}
]
},
{
title: 'My list 2',
_id: '4gfdg5454dfg'
cards: [
{
title: 'my card 22',
listId: '4gfdg5454dfg'
},
{
title: 'my card 22',
listId: '4gfdg5454dfg'
}
]
}
]
members: [
'df76g7gh7gf86889gf989fdg'
]
}
What I've tried?
I've came up with only one thing so far, that is:
Two api calls in mounted hook - one to get the board with lists, second to get all cards.
Then I loop trough lists and loop trough cards and push each card into the list by id?
But this way it seems that my lists would need to have an empty array called cards: [] just for the front-end card-to-list sorting by id, seems somehow wrong.
Is this a good way? Or should I redesign my models and schemas and go with some other way? Any help would be appreciated!
The schema you've defined is pretty good, just one modifications though.
No need to have 'lists' in Board model, since it's already available in lists and also if you keep it in boards, then everytime a new list is added, you'll need to edit the board as well.
Here's how the flow would be.
Initially, when a user signs in, you'll need to show them the list of boards. This should be easy since you'll just do a find query with the user_id on the board collection.
Board.find({members: user_id}) // where user_id is the ID of the user
Now when a user clicks on a particular board, you can get the lists with the board_id, similar to the above query.
List.find({boardId: board_id}) // where board_id is the ID of the board
Similarly, you can get cards with the help of list_id and board_id.
Card.find({boardId: board_id, listId: list_id}) // where board_id is the ID of the board and listId is the Id of the list
Now, let's look at cases wherein you might need data from 2 or more collection at the same time. For example, when a user clicks on board, you not only need the lists in the board but also the cards in that board. In that case, you'll need to write an aggregation as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
}
])
This will return the boards, and in each board, there'll be an array of lists associated with that board. If a particular board doesn't have a list, then it'll have an empty array.
Similarly, if you want to add cards to the list and board, the aggregation query will be a bot more complex, as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
},
// get cards that match the board_id
{
$lookup:
{
from: 'card',
localField: '_id',
foreignField: 'boardId',
as: 'cards'
}
}
])
This will add an array of cards as well to the mix. Similarly, you can get cards of the lists as well.
A bit late to the answer but I think it'll help someone nevertheless. The problem you have could be solved using aggregation framework. While the other answer mentions a pretty good way, it still doesn't have the cards data embedded into it.
MongoDB docs show a way for nested aggregation queries. Nested Lookup
A similar approach could be used for your question.
Board.aggregate([
{
$match: { _id: mongoose.Types.ObjectId(boardId) },
},
{
$lookup: {
from: 'lists',
let: { boardId: '$_id' },
pipeline: [
{ $match: { $expr: { $eq: ['$boardId', '$$boardId'] } } },
{
$lookup: {
from: 'cards',
let: { listId: '$_id' },
pipeline: [{ $match: { $expr: { $eq: ['$listId', '$$listId'] } } }],
as: 'cards',
},
},
],
as: 'lists',
},
},
]);
This will include the cards as an array inside of every list.

Mongodb, storing comments to a specific post

My userSchema looks something like this:
var UserSchema = new Schema({
name: { type: ......},
email: { type..... },
test1: {
[
[0] { test: Array },
[1] { test: Array }
]
}
)};
With a few other objects not included here. In the object that's in test1 there is stored some data to which i want to add or somehow bind an array of comments. The problem is that I'm not sure which is the best way to implement this? If i should create a new commentSchema and somehow connect the comments with the object or how to do it. Can anyone give me some tips on how to implement it?
You can create your schema as follows:
var CommentSchema = new Schema({
user: {type: Schema.Types.ObjectId, ref: 'User', required: true},
message: String,
});
var UserSchema = new Schema({
name: { type: ......},
email: { type..... },
...
test1: {
comments: [CommentSchema]
}
... OR
comments: [CommentSchema]
)};
or if you are trying to do something like posts with comments try
var PostSchema = new Schema({
comments: [CommentSchema]
postBody: String
})
var UserSchema = new Schema({
name: { type: ......},
email: { type..... },
...
posts: [PostSchema]
...
)};

User specific calculated attibute in MEAN.js MongoDB query

I'm building an Restful API using Mean.js.
I have an Article Model:
var ArticleSchema = new Schema({
title: { ... },
content: {...},
...
}
And an User Model, which has an array of the Articles the user bookmarked.
var UserSchema = new Schema({
name: { ... },
...
bookmarks: [{ type: Schema.ObjectId, ref: 'Article' }]
}
I want to attach to the Articles objects retrieved from the DB a calculated boolean attibute 'Bookmarked', indicating if the logged user has bookmarked the article.
I'll also have to repeat the logic to show which articles the user has already read, and which ones are new to him, and maybe an attribute to rate or like/dislike the article. All of them involving the same problem.
How can I do this? How the best way?
I can do this on the DB level or Express level. I'm trying to send to the user the field already calculated in the JSON he'll receive.
I'm open to any solutions, and willing to change the model, relationships or the logic if necessary.
I will suggest something like this;
var ArticleSchema = new Schema({
title: { ... },
content: {...},
...
bookmarkedBy : [{ type: Schema.ObjectId, ref: 'User' }]
readBy : [{ type: Schema.ObjectId, ref: 'User' }]
likedBy : [{ type: Schema.ObjectId, ref: 'User' }]
dislikedBy : [{ type: Schema.ObjectId, ref: 'User' }]
}
var UserSchema = new Schema({
name: { ... },
...
bookmarks: [{ type: Schema.ObjectId, ref: 'Article' }]
likes: [{ type: Schema.ObjectId, ref: 'Article' }]
dilikes: [{ type: Schema.ObjectId, ref: 'Article' }]
bookmarks: [{ type: Schema.ObjectId, ref: 'Article' }]
}

Categories