mongoose populate sort _ Mongoose Node.js - node.js

I have two schemas that represent my post and category documents. I'm trying to sort my posts with category.order property. order property is a number field.
const postSchema = mongoose.Schema({
title: {
type: String,
max: 200,
},
body: {
type: String,
max: 10000,
},
categories: {
type: mongoose.Schema.ObjectId,
ref: 'Category',
required: true
}
})
module.exports = mongoose.model('Post', postSchema);
const categorySchema = mongoose.Schema({
name: {
type: String,
max: 30,
required:true
},
order: {
type: Number,
unique: true
},
})
module.exports = mongoose.model('Category', categorySchema);
I know sort only works with numeric fields. I searched a lot about the simular problem in web and StackOverflow even the mongoose documention.but my query doesn't work. it gets my post back but sorting order is not working.
query:
Post.find({}).populate({path:'categories', select:'order', options:{sort:{order:1}}})

Well populate does not sort the root/outer documents. The options passed in the populate only sorts the inner referenced documents. You have to use aggregation here to make sorting on the parent documents and that's what $lookup can better do as compared to populate queries.
Post.aggregate([
{ '$lookup': {
'from': 'categories',
'let': { 'categories': '$categories' },
'pipeline': [
{ '$match': { '$expr': { '$eq': ['$_id', '$$categories'] }}},
{ '$project': { 'order': 1 }}
],
'as': 'categories'
}},
{ '$unwind': '$categories' },
{ '$sort': { 'categories.order': 1 }}
])

Related

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.

Mongoose: Find all Models using array of objects

I have this Model:
const cart = new mongoose.Schema(
{
products: [{
productId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
},
quantity: {
type: Number,
required: true,
default: 1
},
title: String,
price: Number
}],
},
{ timestamps: true });
How I find all my products (from Model Product) using it.
cart = Cart.find(id);
// inside cart.products
[{productId: 'asvhbajAS13', quantity: 8 },{productId: 'asvhbajAS13', quantity: 2 }]
I want to modify all products after that, is this approach right?
What I've tried:
Product.find({
'_id': { $in: { cart.products } }
}, function(err, product) {
})
});
your code is correct but if you use findOne() .or you can use populate instead of query once more :
cart = Cart.find(id).populate("products")

Populating array of collection which contains references to another collections returns empty array

I have two models Vote and Link,I am trying to populate the votes array in link model,The votes array contains id's that references to the collection Vote,which only contains two fields link and User which also refs to same link model mentioned below and a user model respectively
link Schema:-
const linkSchema = new mongoose.Schema(
{
description: {
type: String,
trim: true,
},
url: {
type: String,
trim: true,
},
postedBy: {
type: mongoose.Types.ObjectId,
ref: "User",
},
votes: [{ type: mongoose.Types.ObjectId, ref: "Vote" }],
},
{
timestamps: true,
}
);
linkSchema.index({ description: "text" });
linkSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Link", linkSchema);
Vote schema:-
const mongoose = require("mongoose");
const voteSchema = new mongoose.Schema({
link: { type: mongoose.Types.ObjectId, ref: "Link" },
user: { type: mongoose.Types.ObjectId, ref: "User" },
});
module.exports = mongoose.model("Vote", voteSchema);
but when i try to get the votes of a link,it always return an empty array ,My function:-
const votes = async ({ id }) => {
const linkData = await Link.findById(id).populate("votes").exec();
console.log(linkData);
};
Output Data:-
{
votes: [], //empty always
_id: 5ecb21059a157117c03d4fac,
url: 'https://www.apollographql.com/docs/react/',
description: 'The best GraphQL client for React',
postedBy: 5ec92a58bf38c32b38400705,
createdAt: 2020-05-25T01:36:05.892Z,
updatedAt: 2020-05-25T01:37:52.266Z,
__v: 0
}
Instead of populate(), you can use aggregate() to get your desired output. This should probably work in your case:
Link.aggregate([
{
$match: {
_id: { $in: [mongoose.Types.ObjectId(id)] // as suggested by the questioner
}
},
{
$lookup: {
from: "vote", // collection to join
localField: "votes", // field from the input documents (filtered after _id is matched)
foreignField: "link", // field to compare with, from other collection
as: "linkData" // output array name
}
}
])
Let me know in the comments.

Mongo DB / Mongoose Conditional Inclusion Search

I have a simple feature that may involve a complicated query. I want to allow users to only be shown to members they've already given approval to find them in my site.
The trouble is I have no idea how to add a conditional and then query within the queried users.
A user calls find.
If the searched users have "Approval Only" selected.
Check to see if the searching user is in their approved list.
I am using NodeJS with Mongoose for my finds.
Please help.
Schema below:
const ProfileSchema = new mongoose.Schema({
userDOBs: {
type: [Date],
required: true
},
gender: {
type: String,
required: true
},
active: {
type: Boolean,
default: true
},
discoverySettings: {
approvedOnly: {
type: Boolean,
default: false
}
},
blockedProfileIDs: {
type: [mongoose.Schema.Types.ObjectId],
ref: "Profile"
},
approvedIDs: [
{
type: [mongoose.Schema.Types.ObjectId],
ref: "Profile"
}
]
});
Query Below:
const profiles = await Profile.find({
$and: [
{
_id: {
$ne: req.user.profileID,
$nin: doNotDisplayList
},
"loc.loc": {
$nearSphere: [long, lat],
$maxDistance: maxDistance
},
userDOBs: {
$lt: moment().subtract(filter.searchParams.ageRange[0], "years"),
$gt: moment().subtract(filter.searchParams.ageRange[1], "years")
},
active: true,
"discoverySettings.visible": true,
blockedProfiles: {
$ne: req.user.profileID
}
}
]
})
.sort({
updatedAt: -1
})
.limit(limit)
.skip(skip);

Add elements in nested document then retrieve the _id

I have the following collection definition:
// Includes
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
// Create required sub schemas
const subSchema0 = new Schema({
value: String,
});
const subSchema = new Schema({
idWordsLibraryName: {
type: Schema.Types.ObjectId,
ref: 'WordsLibrary1_0',
},
type: String,
values: [
subSchema0,
],
});
const schema = new Schema({
version_: String,
idWordsLibraryName: {
type: Schema.Types.ObjectId,
ref: 'WordsLibrary1_0',
},
idsDads: [{
type: Schema.Types.ObjectId,
ref: 'LocationStructure1_0',
}],
params: [
subSchema,
],
});
Summary -> One document with nested parameters with nested values.
I have the following request that add some values into a particular parameter
this.findOneAndUpdate({
_id: data.idLocationStructure,
'params._id': data.idLocationStructureParameter,
}, {
$push: {
'params.$.values': {
$each: dataToPush,
},
},
}, {
new: true,
});
It works as expected.
What I want now is to get the _id of pushed elements, but without loading all values of the parameter.
I have tried to use the select option of findOneAndUpdate but it don't work using the projection:
this.findOneAndUpdate({
_id: data.idLocationStructure,
'params._id': data.idLocationStructureParameter,
}, {
$push: {
'params.$.values': {
$each: dataToPush,
},
},
}, {
new: true,
select: {
'params.$.values': 1,
},
});
It gets me:
{
"_id": "57273904135f829c3b0739dd",
"params": [
{},
{},
{},
{},
],
},
I have tried to perform a second request to get the _ids as well, but it don't work either:
this.find({
_id: data.idLocationStructure,
'params._id': data.idLocationStructureParameter,
}, {
_id: 1,
'params.$.values': {
$slice: -nbAdded,
},
});
If you have any idea of how retrieving the _id of the pushed values without loading all values of the parameter, you are very welcome :)
Well after tons of researches all over the web and stack overflow <3 I have found a solution, which is:
this.aggregate([{
$match: {
_id: new mongoose.Types.ObjectId(data.idLocationStructure),
},
},
{
$unwind: '$params',
}, {
$match: {
'params._id': new mongoose.Types.ObjectId(data.idLocationStructureParameter),
},
},
{
$unwind: '$params.values',
},
{
$sort: {
'params.values._id': -1
},
},
{
$limit: nbAdded,
},
{
$project: {
_id: '$params.values._id',
},
},
]);
If you experience the same problem, here is the explaination:
$match makes me taking the good high level document
$unwind makes me to go into the params array in the document we $match
$match makes me taking the good parameter
$unwind makes me to go into the values array
I $sort all values by _id DESC
I $limit to the number of values I added previsoulsy
I change the name of the _id (like an alias)
So I got as result an array that contains the last added values _ids

Resources