NodeJS + MongoDB Sort Query - node.js

I am trying to sort alphabetically with node and mongodb. This is my current condition, however it does not work:
$addToSet: {
friends: {
$each: [{
_id: req.body.globalUserId.toString(),
username: req.body.globalUserName,
language: req.body.globalUserLanguage,
profilePicture: req.body.globalUserProfilePicture
}],
$sort: {username: 1}
}
}
Here is an example document:
As you can see under the friends array, it goes 'r1' and then 'a' second. Since I would like this array to be sorted alphabetically, the 'a' would go first. How would I do this? Thanks so much!
Here is my Schema for this particular model:
username: String,
email: String,
password: String,
language: { type: String, default: "English" },
profilePicture: { type: String, default: "/images/talk/blank-profile-picture.png" },
pendingFriends: [this],
friends: [this]

Sets are unordered and for the same reason $sort is not supported.
$push will maintain the order.
$push: {
friends: {
$each: [{
_id: req.body.globalUserId.toString(),
username: req.body.globalUserName,
language: req.body.globalUserLanguage,
profilePicture: req.body.globalUserProfilePicture
}],
$sort: {username: 1}
}
}

Related

Mongoose: change specific element in array that matches query

Schema:
var User = new Schema({
userId: String,
name: String,
lastLogin: Date,
lastPost: Date,
followers: [String],
following: [String],
posts: [{
date: Date,
title: String,
likes: Number,
public: Boolean,
comments: [{
date: Date,
comment: String,
postedBy: String
}],
modules:[{
moduleType: String,
entries: [{
file: String,
date: Date
}]
}]
}]
});
Query:
await User.updateOne({
$and:[
{ userId: id },
{ posts: { $elemMatch: { title: activityTitle }}}
]},
{ $inc: { "posts.0.likes": 1 }}
)
.exec()
.then(() => {
console.log(`Liked activity '${activityTitle}'`)
})
This query obviously only increases the likes for the first element in posts.
But what I am trying to do is increase the likes for the post that contains the title: activityTitle.
For example a user, User1, can have 3 posts with titles, post1, post2, post3. But I want to increase the likes on post3. I have no way of knowing which post to like since when I query, it returns the whole array and not just the element with the same title.
You're close to result. You should use dot notation in your use of the $ update operator to do that:
User.updateOne({
$and:[
{ userId: id },
{ posts: { $elemMatch: { title: activityTitle }}}
]},
{ $inc: { "posts.$.likes": 1 }}
)
.exec()
.then(() => {
console.log(`Liked activity '${activityTitle}'`)
})

How to query for sub-document in an array with Mongoose

I have a Schema of Project that looks like this:
const ProjectSchema = new mongoose.Schema({
name: {
type: String,
Required: true,
trim: true
},
description: {
type: String,
},
devices: [{
name: {type: String, Required: true},
number: {type: String, trim: true},
deck: {type: String},
room: {type: String},
frame: {type: String}
}],
cables: {
type: Array
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
adminsID: {
type: Array
},
createdAt: {
type: Date,
default: Date.now
}
I want to query an object from array of "devices".
I was able to add, delete and display all sub-documents from this array but I found it really difficult to get single object that matches _id criteria in the array.
The closest I got is this (I'm requesting: '/:id/:deviceID/edit' where ":id" is Project ObjectId.
let device = await Project.find("devices._id": req.params.deviceID).lean()
console.log(device)
which provides me with below info:
[
{
_id: 6009cfb3728ec23034187d3b,
cables: [],
adminsID: [],
name: 'Test project',
description: 'Test project description',
user: 5fff69af08fc5e47a0ce7944,
devices: [ [Object], [Object] ],
createdAt: 2021-01-21T19:02:11.352Z,
__v: 0
}
]
I know this might be really trivial problem, but I have tested for different solutions and nothing seemed to work with me. Thanks for understanding
This is how you can filter only single object from the devices array:
Project.find({"devices._id":req.params.deviceID },{ name:1, devices: { $elemMatch:{ _id:req.params.deviceID } }})
You can use $elemMatch into projection or query stage into find, whatever you want it works:
db.collection.find({
"id": 1,
"devices": { "$elemMatch": { "id": 1 } }
},{
"devices.$": 1
})
or
db.collection.find({
"id": 1
},
{
"devices": { "$elemMatch": { "id": 1 } }
})
Examples here and here
Using mongoose is the same query.
yourModel.findOne({
"id": req.params.id
},
{
"devices": { "$elemMatch": { "id": req.params.deviceID } }
}).then(result => {
console.log("result = ",result.name)
}).catch(e => {
// error
})
You'll need to use aggregate if you wish to get the device alone. This will return an array
Project.aggregate([
{ "$unwind": "$devices" },
{ "$match": { "devices._id": req.params.deviceID } },
{
"$project": {
name: "$devices.name",
// Other fields
}
}
])
You either await this or use .then() at the end.
Or you could use findOne() which will give you the Project + devices with only a single element
Or find, which will give you an array of object with the _id of the project and a single element in devices
Project.findOne({"devices._id": req.params.deviceID}, 'devices.$'})
.then(project => {
console.log(project.devices[0])
})
For now I worked it around with:
let project = await Project.findById(req.params.id).lean()
let device = project.devices.find( _id => req.params.deviceID)
It provides me with what I wanted but I as you can see I request whole project. Hopefuly it won't give me any long lasting troubles in the future.

how to select a property depending another property - mongodb

I'm using mongoose. I want to select users property depending on another property at this here type.
for example when my type is private I want to select users.
Conversation.find({
users: {
$elemMatch: {
user: _id
}
}
},{
title: 1,
type: 1,
users:1 // when `type` is `private` I want to this field to be one.
});
my Schema:
const ConversationSchema = new Schema({
type: {type: String, enum: ['private', 'group'], default: 'private'},
creator: {type: Schema.Types.ObjectId, ref: 'User', index: true, required: true}, // creator
// for group,
title: String,
picture: String,
description: String,
users: [
{
user: { type: Schema.Types.ObjectId, index: true, reuqired: true, unique: true },
role: { type: String, enum: ['admin', 'member'], default: 'member' },
mute: { type: Boolean, default: false },
type: {type: String, enum: ['private', 'group'], default: 'private'},
}
],
}, { timestamps: true });
You can conditionally exclude fields by using REMOVE in aggregation. In your case, it should be:
Conversation.aggregate([
{$match: {"users.user": id}},
{
$project: {
title: 1,
type: 1,
users: {
$cond: {
if: { $eq: [ "$type", "private" ] },
then: "$users",
else: "$$REMOVE"
}
}
}
}
])
Side note: If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.
You can use aggregate like this, it will select all users if you want to users detail then will have to use populate.
db.getCollection("Conversation").aggregate([
{
$unwind: "$users"
},
{
$match: {
"type": 'private'
}
}
]);

Mongodb update push Array of Objects

I am unable to solve this issue (and looking to avoid loop update one by one), please hep me out here.
I have a fambio document (which has its own FamilyModel) which gets created after user gives his below information:
{
name: 'john',
lname: 'doe',
}
Now, after above information gets saved, user provides more information about the family after some processing in backend:
let familyArr = [
{ _id: 1234, name: 'Jenny', lname: 'doe', relation: 'mother' },
{ _id: 2345, name: 'Jawn', lname: 'doe', relation: 'father' },
{ _id: 3456, name: 'Jane', lname: 'doe', relation: 'sister' },
{ _id: 4567, name: 'Daisy', lname: 'wick', relation: 'pupper' }
]
Altogether, FamilyModel schema looks like:
const FamilyModel = mongoose.Schema({
name: {type: String, required: true},
lname: {type: String, required: true},
family: [relationshipSchema]
}, {
timestamp: true
});
const relationshipSchema = mongoose.Schema({
name: {type: String, required: true},
lname: {type: String, required: true},
relation: {type: String, required: true}
}, {
required: false,
timestamp: true
});
Now, John has an array of objects of family (filed type Array) and trying to insert that Array of Object like this:
What I tried multiple options:
db.fambio.updateOne({_id: 1111}, { $set: { family: familyArr }})
db.fambio.findOneAndUpdate({_id: 1111}, { $push: { family: familyArr }});
db.fambio.update({_id: 1111}, $addToSet: { 'family': familyArr}});
Nothing is working with respect to insert the constructed Object directly to the field. When I insert one at a time, it gets updated.
How am I supposed to write the query to update/append an Array of Objects in to a field of Array Type having its own Schema maintained.
Okay, I've solved it. How I solved it:
Initially, I saved the document and did some processing on the info as per my requirements post which I wanted to insert Array of Elements in to an Array key field where I was running in to the issue of nothing getting inserted. What I was missing was $each and this is how I did it:
db.getCollection('fambio').update({
_id: johnsuserid
}, {
$push: {
'family': {
$each:[
{ _id: 1234, name: 'Jenny', lname: 'doe', relation: 'mother' },
{ _id: 2345, name: 'Jawn', lname: 'doe', relation: 'father' },
{ _id: 3456, name: 'Jane', lname: 'doe', relation: 'sister' },
{ _id: 4567, name: 'Daisy', lname: 'wick', relation: 'pupper' }
]
}
}
});
Thank you everyone!! Hope this helps someone who may face this problem in future.

How I can select only one field from every object in array using Mongoose?

I have schema:
{
name: String,
surname: String,
note: String,
someField: String,
secondSomeField: String
blackList: [{
userId: mongoose.Types.ObjectId,
reason: String
}]
}
I need select document with all fields, but in blackList field I need select only userId. Example what I want:
{
name: "John",
surname: "Doe",
note: "bwrewer",
someField: "saddsa",
secondSomeField: "sadsd",
blackList: [58176fb7ff8d6518baf0c931, 58176fb7ff8d6518baf0c932, 58176fb7ff8d6518baf0c933, 58176fb7ff8d6518baf0c934]
}
How I can do this? When I do
Schema.find({_id: myCustomUserId}, function(err, user) {
//{blackList: [{userId: value}, {userId: value}]}
//but i need
//{blackList: [value, value, value]}
})
If you want to hide the field by default:
{
name: String,
surname: String,
note: String,
someField: String,
secondSomeField: String
blackList: [{
userId: mongoose.Types.ObjectId,
reason: {type:String, select:false}
}]
}
If you simply want to exclude in that query:
Schema
.find({_id: myCustomUserId})
.select("-blackList.reason")
.exec(function (err, user) {
//your filtered results
})
Not tested but they should work both.
Schema.aggregate( {$match: {_id: mongoose.Types.ObjectId(myCustomId)} }, {$project: {blackList: "$blackList.userId", name: true, surname: true, someField: true}} ).exec(fn)

Resources