Undesirable structure when populating sub document with Mongoose - javascript

A user has project IDs but I also want to store some additional project info:
const userSchema = new Schema({
...
projects: [{
_id: {
type: Schema.Types.ObjectId,
ref: 'Project',
unique: true, // needed?
},
selectedLanguage: String,
}]
});
And I want to populate with the project name so I'm doing:
const user = await User
.findById(req.user.id, 'projects')
.populate('projects._id', 'name')
.exec();
However user.projects gives me this undesirable output:
[
{
selectedLanguage: 'en',
_id: { name: 'ProjectName', _id: 5a50ccde03c2d1f5a07e0ff3 }
}
]
What I wanted was:
[
{ name: 'ProjectName', _id: 5a50ccde03c2d1f5a07e0ff3, selectedLanguage: 'en' }
]
I can transform the data but I'm hoping that Mongoose can achieve this out the box as it seems a common scenario? Thanks.

Seems like there are two options here:
1) Name the _id field something more semantic so it's:
{
selectedLanguage: 'en',
somethingSemantic: { _id: x, name: 'ProjectName' },
}
2) Flatten the data which can be done generically with modern JS:
const user = await User
.findById(req.user.id, 'projects')
.populate('projects._id', 'name')
.lean() // Important to use .lean() or you get mongoose props spread in
.exec();
const projects = user.projects.map(({ _id, ...other }) => ({
..._id,
...other,
}));

try something like this
populate({path:'projects', select:'name selectedLanguage'})

Related

Unable to save a array correctly in mongoodb using node/mongose

What is the proper way to save an array of data in mongodb using node/mongoose?
I am able to all my data as an array, but everything is saved as the first value in the array (see img)
this is my current code:
const channel = new Channel({
// other fields
fields:req.body.fields,
creator: req.userData.userId //fix: creator not being passed
});
channel
.save()
.then(createdChannel => {
res.status(201).json({
message: "Channel added successfully",
channel: {
...createdChannel,
id: createdChannel._id
}
});
})
.catch(error => {
console.log('error',error)
res.status(500).json({
message: "Creating a channel failed!"
});
});
here is my data model:
const mongoose = require("mongoose");
const channelSchema = mongoose.Schema({
// other fields
fields: { type: [String], required: true },
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});
module.exports = mongoose.model("Channel", channelSchema);
I didn't see a similar issue online.
Any idea on how I am suppose to save an array properly?
Looks like your req.body.fields is stringified. So, you should parse it back to JSON:
const channel = new Channel({
fields: JSON.parse(req.body.fields),
creator: req.userData.userId,
});
This is because you are setting fields value as an array of strings, regarding to this question you should define your schema as:
fields : [{foo: String, bar: string}]
or if you don't know what attributes you will have in the object:
fields: {"type": Array, "default": []}
Hope this can help you

how to use find function in nodejs

const categorySchema = mongoose.Schema({
name: {
required: true,
type: String
}
});
const productSchema = mongoose.Schema({
name: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.Mixed,
ref: "Category",
required: true
}
});
As you see above I have two models where I am using Category inside Product.
Now I want to fetch all the products by passing a particular Category or its id as _id which gets generated by default by mongodb.
Although I am achieving the desired result do this below:
const id = req.query.categoryId;//getting this from GET REQUEST
let tempProducts = [];
let products = await Product.find({});
products.forEach(product => {
if (product.category._id===id){
tempProducts.push(product);
}
});
It gets me what I want to achieve but still I want to know how to get it using "find" function. or this what I am doing is the only way.
Ok thank you all for your time. I found out the solution which is:
First of all below is my schema as Product which includes another schema named Category:
const productSchema = mongoose.Schema({
name: { type: String, required: true },
category: { type: mongoose.Schema.Types.Mixed, ref: "Category", required: true } });
And to get all the products related to a particular category,
We need to add our query inside quotes like this:
let tempProducts = [];
tempProducts = await Product.find({ "category._id": id});
This is how I achieved as I wanted and working as expected.

Populate and Aggregate MongoDB documents

I am currently working with mongoose ODM and MongoDB. Currently, I have faced a tiny issue that I can't seem to get going. So I have a User collection like so:
const userSchema = new Schema({
name: String,
posts: [{type: Schema.Types.ObjectId, ref: 'Post'}],
createdAt: Date,
updatedAt: Date
})
and a Post collection like so:
const postSchema = new Schema({
text: String,
user: {type: Schema.Types.ObjectId, ref: 'User'},
createdAt: Date,
updatedAt: Date
})
The user collection has a posts field embedded in it that is an array of the user posts. A typical example of what it looks like is given below:
{
_id: 56we389iopks,
name: John,
posts: ['6748ufhsgshsklop...', '5e43tiodo...']
}
A user has an array of posts, I find the user by their respective ID and populate the posts array.
AIM
I want to be able to fetch a user by their ID and get all posts, which are sorted with the newest and oldest post. I know mongoose has an aggregate method, but I don't know how to go about this. Thank you, any help will be appreciated.
Example of expected output document:
const user = {
_id: 5e34647489930hhff494,
name: John,
posts: [
{
text: 'aloha',
createdAt: 2021-08-30 // newest post
updatedAt: 2021-08-30
},
{
text: 'heyyy',
createdAt: 2021-02-14 // oldest post
updatedAt: 2021-02-14
},
]
}
You have the #Populate from Mongoose that can help you out.
Let's try the following
Let's retrieve posts by User
const postsByUser = await User
.find({ _id: '...' })
.populate('posts');
console.log('postsByUser', postsByUser);
Once we're able to retrieve the posts, let's improve it ordering them by the createdAt
const postsByUserOrdered = await User
.find({ _id: '...' })
.populate({
path: 'posts',
options: { sort: { 'createdAt': -1 } }
})
.exec();
console.log('postsByUserOrdered', postsByUserOrdered);

MongoDB Aggregate is not matching specific field

I'm new to Aggregation in MongoDB and I'm trying to understand the concepts of it by making examples.
I'm trying to paginate my subdocuments using aggregation but the returned document is always the overall values of all document's specific field.
I want to paginate my following field which contains an array of Object IDs.
I have this User Schema:
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
firstname: String,
lastname: String,
following: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
...
}, { timestamps: true, toJSON: { virtuals: true }, toObject: { getters: true, virtuals: true } });
Without aggregation, I am able to paginate following,
I have this route which gets the user's post by their username
router.get(
'/v1/:username/following',
isAuthenticated,
async (req, res, next) => {
try {
const { username } = req.params;
const { offset: off } = req.query;
let offset = 0;
if (typeof off !== undefined && !isNaN(off)) offset = parseInt(off);
const limit = 2;
const skip = offset * limit;
const user = await User
.findOne({ username })
.populate({
path: 'following',
select: 'profilePicture username fullname',
options: {
skip,
limit,
}
})
res.status(200).send(user.following);
} catch (e) {
console.log(e);
res.status(500).send(e)
}
}
);
And my pagination version using aggregate:
const following = await User.aggregate([
{
$match: { username }
},
{
$lookup: {
'from': User.collection.name,
'let': { 'following': '$following' },
'pipeline': [
{
$project: {
'fullname': 1,
'username': 1,
'profilePicture': 1
}
}
],
'as': 'following'
},
}, {
$project: {
'_id': 0,
'following': {
$slice: ['$following', skip, limit]
}
}
}
]);
Suppose I have this documents:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'gagi',
following: []
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'kuku',
following: []
},
{
_id: '76jghkdfhasjhfsdkf',
username: 'john',
following: ['5fdgffdgfdgdsfsdfsf', '5fgjhkljvlkdsjfsldkf']
},
]
And when I test my route for user john: /john/following, everything is fine but when I test for different user which doesn't have any following: /gagi/following, the returned result is the same as john's following which aggregate doesn't seem to match user by username.
/john/following | following: 2
/kuku/following | following: 0
Aggregate result:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'kuku',
...
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'gagi',
...
}
]
I expect /kuku/following to return an empty array [] but the result is same as john's. Actually, all username I test return the same result.
I'm thinking that there must be wrong with my implementation since I've only started exploring aggregation.
Mongoose uses a DBRef to be able to populate the field after it has been retrieved.
DBRefs are only handled on the client side, MongoDB aggregation does not have any operators for handling those.
The reason that aggregation pipeline is returning all of the users is the lookup's pipeline does not have a match stage, so all of the documents in the collection are selected and included in the lookup.
The sample document there is showing an array of strings instead of DBRefs, which wouldn't work with populate.
Essentially, you must decide whether you want to use aggregation or populate to handle the join.
For populate, use the ref as shown in that sample schema.
For aggregate, store an array of ObjectId so you can use lookup to link with the _id field.

Mongoose - Cannot populate with sort on path notifications.from because it is a subproperty of a document array

I have a very simple mongo scheme I'm accessing with mongoose
I can map the username and firstname to each notification's from field by using populate, the issue is I can't seem to get any sorting to work on the date field
With this code I get an error of
MongooseError: Cannot populate with sort on path notifications.from
because it is a subproperty of a document array
Is it possible to do this a different way, or newer way (deep populate, virtuals)? I'm on Mongoose 5.
I'd rather not use vanilla javascript to sort the object afterwards or create a separate schema
var UserSchema = new Schema({
username: String,
firstname: String,
notifications: [
{
from: { type: Schema.Types.ObjectId, ref: 'User'},
date: Date,
desc: String
}
]
});
app.get('/notifications', function(req, res) {
User.findOne({ _id: req._id }, 'notifications')
.populate({
path: 'notifications.from',
populate: {
path: 'from',
model: 'User',
options: { sort: { 'notifications.date': -1 } }
}
})
.exec(function(err, user) {
if (err) console.log(err)
})
});
That possible duplicate is almost 2 years old about Mongo. I'm asking if there are newer or different ways of doing this in Mongoose as it has changed a bit since 2016 with newer features.
From Mongoose V5.0.12 FAQ : http://mongoosejs.com/docs/faq.html#populate_sort_order
Q. I'm populating a nested property under an array like the below
code:
new Schema({
arr: [{
child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
}] });
.populate({ path: 'arr.child', options: { sort: 'name' } }) won't sort by arr.child.name?
A. See this GitHub issue. It's a known issue but one that's
exceptionally difficult to fix.
So unfortunately, for now, it's not possible,
One way to achieve this is to simply use javascript's native sort to sort the notifications after fetching.
.exec(function(err, user) {
if (err) console.log(err)
user.notifications.sort(function(a, b){
return new Date(b.date) - new Date(a.date);
});
})
It can be achievable using nesting populate like this -
eg - schema - {donationHistory: {campaignRequestId: [ref ids]}}
await user.populate({
path: 'donationHistory.campaignRequestId',
populate: [{
path: 'campaignRequestId',
model: 'CampaignRequest',
options: { sort: { 'createdAt': -1 } },
}],
...deepUserPopulation,
}).execPopulate();

Categories

Resources