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.
Related
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?
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.
This question already has answers here:
Mongoose populate vs object nesting
(1 answer)
MongoDB relationships: embed or reference?
(10 answers)
Closed 5 years ago.
I have a question, wondering if there is any way to persist the returned document when using the Mongoose .populate() function by saving it back to the model. Also some questions on how to structure things. Here are my schemas:
var clientSchema = new Schema({
phone: String,
email: String
},
);
var menuSchema = new Schema({
itemName: String,
itemPrice: Number,
});
var transactionSchema = new Schema ({
createdBy: { type: Schema.ObjectId, ref: 'Client'},
items: [{ type: Schema.ObjectId, ref: 'Menu' }],
});
var Menu = mongoose.model('Menu', menuSchema);
var Client = mongoose.model('Client', clientSchema);
var Transaction = mongoose.model('Transaction', transactionSchema);
When I create a new Transaction with a POST request, I populate it and return the populated Transaction as a response:
{
"_id": "5a0bde94f4434c0a604341d2",
"createdBy": {
"_id": "5a0a8a3f9c348f0998ba8c2c",
"phone": "1234567890",
"email": "some#thing.com"
},
"__v": 0,
"items": [{ Many Menu objects }]
}
However, when I query the DB again with GET, I get this:
{
"_id": "5a0bde94f4434c0a604341d2",
"createdBy": "5a0a8a3f9c348f0998ba8c2c",
"__v": 0,
"items": [Array of ObjectIds]
}
I can't use .save() because the original schema only accepts ObjectId, not an entire Object.
I noticed that when I made my schema include SubDocuments, I did not really need to use the .populate() function. I simply pushed an object into the array, and it would be there when queried.
var transactionSchema = new Schema ({
createdBy: { type: Schema.ObjectId, ref: 'Client'},
items: [menuSchema], // Sub Doc
});
MongoDB Docs say creating large mutable arrays is a bad design. I could see some transactions having 50 or 100 objects. I can see this being more of a problem if I use subDocuments because file size , but I could also imagine that doing a .populate() on an array of 100 object IDs may be expensive.
I need to update the items array in the transaction schema every time the client registers an onclick function. I need to re-render that to the client, which involves a single PUT request per click. But I have to parse that array with each click, one by one. If I'm doing a .populate() on every single item in the array...that's not great. But using subDocuments would increase the filesize of the database.
I previously had a simpler schema, but thought that passing by reference would increase the integrity of the prices being rendered. Is it better to just have an array of Objects and push into that?
var transactionSchema = new Schema ({
createdBy: { type: Schema.ObjectId, ref: 'Client'},
items: [{
name: {type: String},
price: {type: Number}
}]
});
I have a collection Playlist that contains an array of items
{
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [
{
id: { // do not mix up with _id, which is the autogenerated id of the pair {id,type}. ID is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
}
]
}
Mongo automatically adds the _id field to the items when I push a pair {id,type} to items (but I don't care about it).
Now I would like to remove several "pairs" at once from the items array.
I have tried using $pullAll but it requires an exact match, and I do not know the _id, so it does not remove anything from items
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
I have tried using $pull with different variants, but it removed ALL objects from items
playlistModel.update({userId:userId},{$pull:{items:{"items.id":{$in:["123","456"]}}}},null,function(err){
playlistModel.update({userId:userId},{$pull:{items:{$in:[{id:"123",type:"video"},{id:"456",type:"video"}]}}},null,function(err){
Am I missing something or am I asking something that isn't implemented?
If the latter, is there a way I can go around that _id issue?
OK I found a way that works using $pull:
playlistModel.update({userId:userId},{$pull:{items:{id:{$in:["123","456"]}}}},null,function(err){
It doesn't take the type into account but I can't see any issue with that since the id is unique across all types anyway.
Although I will wait a bit to see if someone has a better solution to offer
EDIT
With Veeram's help I got to this other solution, which IMO is more elegant because I don't have _ids that I don't need in the database, and the $pullAll option seems more correct here
var playlistItemSchema = mongoose.Schema({
id: { // do not mix up with autogenerated _id. id is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
},{ _id : false });
var schema = new Schema({
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [playlistItemSchema]
});
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
tips:
you can use _id field to handle your playlistModel data.
mongoose api : new mongoose.Types.ObjectId to generate an Object_id
let _id=new mongoose.Types.ObjectId;
playlistModel.updateMany({_id:_id},{ $set: { name: 'bob' }}).exec(data=>{console.log('exec OK')});
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) {
});