Mongoose populate single element of array - arrays

Is there a simple way to only populate a single element of an array of object ids stored in a mongoose document?
The problem
Let's assume the following Schema
const userSchema = new Schema({
username: {type: String, required: true},
posts: [{type: Types.ObjectId, ref: 'Posts'}],
})
Now if I want to populate the first post of a specific user I would try this:
doc.populate('posts.0');
Mongoose does not throw any kind of error but does not populate the first post.
I don't understand this behavior, because with this schema
const userSchema = new Schema({
username: {type: String, required: true},
posts: [{postRef: { type: Types.ObjectId, ref: 'Posts' }}],
})
and this command
doc.populate('posts.0.postRef');
everything turns out as expected.
My use case
In my case every user can only access some fields of some of the posts stored in the database. So my idea was to iterate over every post id and select the accessible fields to populate for.
My current code looks like this:
for (let i = 0; i < doc.posts.length; i++) {
const postId = doc.posts[i];
if (user.canAccessPost(postId) {
const fields = user.getFieldsForPost(postId);
await doc.populate({
path: `posts.${i}`,
select: fields,
})
}
}
My mongoose version is 6.1.
Is there any way to populate just one array element or should I start looking for another solution to achieve this?

Related

How to save each item in an array as a document using mongoose ? If any document matches item in array, it should not be created

I have a categorySchema in mongoose like so :
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const categorySchema = new Schema({
name:{
type: String,
required:true
}
},{timestamps:true});
module.exports = mongoose.model('Category',categorySchema);
I will get an array from request like so :
const categories = ['phones','computer','bikes','cars'];
I already have bikes and cars in my categories collection. I can iterate through each item in the array and save it as a document. But I need each document to be unique. No two documents with same name (Obviously). I guess I can find the existing documents using $in in the find query and remove each documents by iterating through the array of documents I get after that I can iterate through the array I got from the request and save each item as new documents. But I wanted to know if there is a better way of doing it.
You can write your schema like this:
const categorySchema = new Schema({
name:{
type: String,
required:true,
unique: true
}
},{timestamps:true});

Upsert ref documents when pushing to array in Express

I have a Candidate schema with an array of refs to an Endorser schema. Like so:
const CandidateSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
endorsements: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Endorser'
}]
});
const EndorserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
});
I receive endorsements in the form of an array of strings (i.e., the endorser's name). I want to iterate through that array and retrieve the _id of each from the Endorser model or upsert a new one if it doesn't exist. I then want to push those refs onto the existing candidate instance.
The following code works, but I really don't feel comfortable modifying the candidate instance in-memory. Having to do the promise resolution separately also seems weird.
const endorsementPromises = endorsements.map(async endorser => {
let endorserObj = await Endorser.findOneAndUpdate({name: endorser}, {name: endorser}, {upsert: true, new: true});
return endorserObj._id;
});
const endorsementArray = await Promise.all(endorsementPromises);
candidate.endorsements = candidate.endorsements.concat(endorsementArray);
await candidate.save();
I have tried using findOneAndUpdate with $push and $each. However, this only returns an error and doesn't update the document.
Candidate.update(
{id: candidate._id},
{$push: {
endorsements: {
$each: endorsementArray
}
}}
);
// the error
Error: {"n":0,"nModified":0,"ok":1}
I'm not sure why $push and $each aren't updating the document.
Any guidance would be really appreciated.
Try using $addToSet instead of $push. Also, it seems like you should be matching on _id instead of id in your update.

Pushing onto Mongo SubDoc of SubDoc array

I'm going around in circles with this one so hoping someone can help. I'm building a nodejs application that receives sensor values from nodes. There can be multiple sensors on a node.
Using NodeJS, Mongod DB and Mongoose, all running on a raspberry pi, 3 I've built the following Schemas & Model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var valueSchema = new Schema ({
timestamp: {type: Date},
value: {}
});
var sensorSchema = new Schema ({
id: {type: Number},
type: {type: String},
description: {type: String},
ack: {type: Boolean},
values: [valueSchema]
});
var SensorNode = mongoose.model('SensorNode', {
id: {type: Number, required: true},
protocol: {},
sensors: [sensorSchema]
});
I can add in the node, and push sensors onto the sensors array, but I seem unable to push values onto the values array.
I've looked over a few other examples and questions on similar issues, and looked at using populate, but cant seem to get them to work.
Here is my code:
function saveValue(rsender, rsensor, payload) {
var value = {
values: {
timestamp: new Date().getTime(),
value: payload
}
}
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$": value
}
}, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
}
This is returning undefined for the result and this error:
MongoError: exception: Cannot apply $push/$pushAll modifier to non-array
Values is definitely an array in the sensor schema.
I'm using readable ids rather than the auto assigned Mongo DB IDs for the sake of the UI, but I could use the MongoDB _id if that makes any difference, I don't see why it would?
Where am I going wrong ?
You're using positional operator $ so let's check the docs
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
So sensors.$ will return one particular document from your sensors array. That's why you're getting an error. On this level of your document you can only replace this item by using $set. I bet you wanted to do something like this:
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$.values": payload
}
});
This operation will just append payload to values array in one particular sensor with id equal to rsensor.

mongoose query: find an object by id in an array

How could I find an image by id in this Schema. I have the id of the User and the id of the image I am looking for. What would be the best way to do this and do all images in this case have different ids or could they have the same id because they don't belong to the same User?
My Schema looks like this:
var userSchema = new Schema({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
name: String,
about: String,
images: [{
id: Schema.ObjectId,
link: String,
main: Boolean
}]
});
When you are interested in the full object it is a simple find:
.find({"facebook.id":"<id>", "images.id":<image-id>})
I don't think that there is a way to reduce the image array in the result.
To update a single element in the image array you can use this:
.update({"facebook.id":"<id>", "images.id":<image-id>}, {$set : {"images.$.main" :false} } );
userSchema .find({facebook.id: "some ID",{ "images.id": { $in: [ id1, id2, ...idn] }}
since images are inside the document you can have same ID's however every time you query you should keep in mind that you send some other parameters such as facebook.id or facebook.email along with image id's to retrieve them. Otherwise you end up getting all that might be irrelevant only because you decide to keep same ID's for images.
tl;dr
I struggled with this and came up with a solution. Like you, I was trying to query for a deeply nested object by the _id, but I kept coming up empty with the results. It wasn't until I did some type checking that I realized the id value I was getting from my frontend, while directly supplied by mongoose, was in fact a String and not an Object.
I realize this question was already partially answered before, but that person's solution didn't work for me, and the comment on the answer tells me you wanted to update the specific image you queried for, which is exactly what I was trying to do.
The solution
In order to select an object from the nested array by the _id value, first you'll have to install the npm package bson-objectid and use the provided method to convert your string into an objectId in your query.
In your terminal:
npm i bson-objectid
In your code:
const ObjectId = require('bson-objectid')
userSchema.findOneAndUpdate(
{ "facebook.id": <user-id>, "images._id": ObjectId(<image-id>) },
{ "$set": { "images.$.main": false } },
{ new: true }, // an extra options parameter that returns the mutated document
(err, user) => {
if (err) {
handleErr(err)
} else {
console.log(user)
// do something with new user info
}
)

Mongoose query array of ObjectId's for one ObjectId

Trying to query a subdocument array which contains ObjectId refs, and find one item.
The items are ObjectId's and get populated when I add a new lesson and 'assign' it to a specific location.
This mongoose schema shows what i mean.
var mongoose = require('mongoose');
var lessonNames = new mongoose.Schema({
day: {type: String },
name: {type: String},
startTime: {type: String},
endTime: {type: String}
});
var locationNames = new mongoose.Schema({
name: {type: String, required: true},
address: String,
lessons: [ { type: mongoose.Schema.Types.ObjectId, ref: 'lessonnames'}]
});
mongoose.model('lessonnames', lessonNames);
mongoose.model('locationnames', locationNames);
I have added a lesson to one location, and now i'm trying to find this specific lesson. I've tried a couple of methods, but none worked for me. Every time i get a null or undefined reported when i output the query to the console.log.
if (location.lessons && location.lessons.length > 0) {
loc
.findById(req.params.locationid)
.populate('lessons', '_id')
.exec(function (err, myLesson) {
console.log(myLesson.lessons)
});
When i execute the above statement, i see the following being generated in the nodejs console, with mongoose debugging enabled.
Mongoose: locationnames.findOne({ _id: ObjectId("56d4b687c4bcb5681a870cb5") }) { fields: undefined }
GET /api/locations/56d4b687c4bcb5681a870cb5/lesson/56d4b687c4bcb5681a870cb4
Mongoose: lessonnames.find({ _id: { '$in': [ ObjectId("56d4b687c4bcb5681a870cb4") ] } }) { fields: { _id: 1 } }
The code line below is the output from console.log. As you can see, the ObjectId is shown as part of the lessons array. But how do I make a query which 'selects' this ObjectId, so i can reference with it to a lesson.
[{"_id":"56d4b687c4bcb5681a870cb4"}]
Search the web, and saw some posts about the _id nog being a string type, and i should cast it to be a string. But i think it should be possible with ObjectId and it should be rather easy to query for it, but i lack good knowledge of mongoose and mongodb to get it working.
Any help would be appreciated!
Please try it as below, the {_id: "56d4b687c4bcb5681a870cb4"} in populate to match the ids in lessons array.
loc.findById(req.params.locationid)
.populate('lessons', null, {_id: "56d4b687c4bcb5681a870cb4"})
.exec(function (err, location) {
});

Resources