Mongoose: insert object into a sub-document array - javascript

I am trying to insert a time object into the times array for a specific activity name for a specific user. For example, if the user was "someuser" and I wanted to add a time to the times for guitar I am unsure as to what to do.
{
username: "someuser",
activities: [
{
name: "guitar",
times: []
},
{
name: "code",
times: []
}
]
}, {
username: "anotheruser",
activities: []
}
This is currently the function that I have, I cannot figure out what I am doing wrong, any help would be greatly appreciated:
function appendActivityTime(user, activityName, newRange) {
User.updateOne(
{username: user, 'activities.name': activityName},
{ $push: {'activities.$.times': {newRange}},
function (err) {
if (err) {
console.log(err);
} else {
console.log("Successfully added time range: " + newRange);
}
}}
);
}
appendActivityTime("someuser", "guitar", rangeObject);

i've tried your attempt and it worked for me:
db.getCollection("test").updateOne(
{ username: "someuser", "activities.name": "guitar" },
{ $push: { "activities.$.times": { from: ISODate(), to: ISODate() } } } //Don't worry about ISODate() in node.js use date objects
)
results:
{
"_id" : ObjectId("5f6c384af49dcd4019982b2c"),
"username" : "someuser",
"activities" : [
{
"name" : "guitar",
"times" : [
{
"from" : ISODate("2020-09-24T06:15:03.578+0000"),
"to" : ISODate("2020-09-24T06:15:03.578+0000")
}
]
},
{
"name" : "code",
"times" : [
]
}
]
}
what i would suggest you using instead is arrayFilter, they are much more precise and when you get used to them, they became very handy

If you are not confident with updating nested documents, let mongoose make the query.
let document = await Model.findOne ({ });
document.activities = new_object;
await document.save();

Related

Mongoose - Update an object inside nested array

My Mongo schema looks like this,
I want to update a flashcard object located in an array of flashcard also located in an array of subject.
const classrooms = new mongoose.Schema({
name: String,
year: String,
student: [
{
firstname: String,
lastname: String,
mail: String,
userId: String,
}
],
subject: [
{
subjectId: String,
flashcard: [
{
title: String,
tag: []
}
]
}
]
});
What I am doing is
const flashcard = await classroomModel.findOneAndUpdate({
_id : classroomId,
"subject" : {
"subjectId" : subjectId,
"subject.flashcard" : {
"_id" : flashcardId
}
},
"$set" : {
"flashcard.title" : "new title"
}
})
But this is deleting all flashcards located inside an object.
Any help would be appreciated.
You need arrayFilters to specify the (to-be-updated) flashcard document that meets the criteria for subject and flashcard.
db.collection.update({
_id: 1//classroomId,
},
{
"$set": {
"subject.$[subject].flashcard.$[flashcard].title": "new title"
}
},
{
arrayFilters: [
{
"subject.subjectId": 1//subjectId
},
{
"flashcard._id": 1//flashcardId
}
]
})
Sample Mongo Playground
Although i accepted #yong shun which is the best approach, another way to do it in case you don't like mongoose syntax :
const classroom = await classroomModel.findOne(
{
_id: 1 //classroomId,
},
)
const subject = classroom.subject.find(
(currentSubject: any) => {
return currentSubject.subjectId == 1 //subjectId
}
)
const flashcard = subject.flashcard.find(
(currentFlashcard: any) => {
return currentFlashcard._id == 1 //flashcardId
}
)
flashcard.title = "my new title";
await classroomModel.updateOne(classroom);

updateMany() on array

In my application i have a collection called Blog and i run this query every 24h
await Blog.updateMany({}, [
{
$set: {
viewed: {
$add: [{ $size: "$visitorIps" }, "$viewed"]
},
visitorIps: []
}
}
]);
My problem is that i have a second collection called Users.
Inside of Users i have an array called posts and here are all posts from that user saved.
{
_id: 234klj2รถ34,
user: "Max",
posts: [
{
_id: 5dgewef323523,
name: "My first blogpost",
content: "...",
viewed: 0,
visitorIps: ["192.168.23.12"]
}
]
}
Now i need the same query on my second collection for each array. How do i do it? I tried something like this but it doesnt worked:
await User.updateMany({}, [
{
$set: {
"posts.$[].viewed: {
$add: [{ $size: "posts.$[].visitorIps" }, "posts.$[].viewed"]
},
"posts.$[].visitorIps": []
}
}
]);
But thats completely wrong. Could somebody help me here out?
You can try using $map,
your logic and code remain same for viewed and visitorIps
$mergeObjects will merge current cursor fields and viewed and visitorIps that we have calculated
await User.updateMany({},
[{
$set: {
posts: {
$map: {
input: "$posts",
as: "post",
in: {
$mergeObjects: [
"$$post",
{
"viewed": {
$add: [{ $size: "$$post.visitorIps" }, "$$post.viewed"]
},
"visitorIps": []
}
]
}
}
}
}
}]
)

How to pull an object from array in MongoDB

I have pull friend request when it is accepted or canceled. So, when I try to pull it from array, it doesn't work.
It matches for 1 document but 0 document modified.
When I delete the requested_at field from user field, it works well.
Where do I make mistake at ?
MongoDB Document
{
"_id" : ObjectId("5cb18680aa024b2d441f93cc"),
"friends" : [],
"friend_requests" : [
{
"user" : {
"id" : ObjectId("5cb14fd7db537905c89e0a72"),
"requested_at" : ISODate("2019-04-14T17:51:00.588Z")
}
}
]
}
MongoDB Query
db.getCollection('users').updateOne(
{ _id: ObjectId("5cb18680aa024b2d441f93cc") },
{
$pull: {
friend_requests: {
user: {
id: ObjectId("5cb14fd7db537905c89e0a72")
}
}
}
});
Result
{
"acknowledged" : true,
"matchedCount" : 1.0,
"modifiedCount" : 0.0
}
Use dot notation to specify a condition:
db.users.updateOne(
{ _id: ObjectId("5cb18680aa024b2d441f93cc") },
{
$pull: {
"friend_requests": {
"user.id": ObjectId("5cb14fd7db537905c89e0a72")
}
}
});

Node mongoose populate with condition not returns the expected results

I am trying to use the query conditions in populate method.
if a condition is used then still all the records are populated but ones that don't satisfy the condition have the populated field is set to null,
For example:
var query = BookModel.find();
query.populate('author','name',{name:'author1'});
query.find(function(err,books){
console.log(books);
});
Then the output is:
[ { author: { _id: 4ea0db52e09aa6aad2e831fe, name: 'author1' },
title: 'book1',
_id: 4ea0dbebc191848704000005 },
{ author: null,
title: 'book2',
_id: 4ea0dbebc191848704000006 } ,
{ author: null,
title: 'book3',
_id: 4ea0dbebc191848704000007 } ,
{ author: null,
title: 'book4',
_id: 4ea0dbebc191848704000008 } ]
However, I expect only the 1st record in the output result. How can i solve this problem?
I had same problem which you are facing and I tried the above code but it did not help me solve the problem. I found a way to do it just like you want. You cannot do this kind of filter by using populate, you have to use raw query of mongodb to filter out your data.
// db['result'] is the name of database
db['result'].aggregate([
// this is just like populate this populate the user field
{
$lookup:{
from:'users',
localField:'user',
foreignField:'_id',
as:'user'
}
},
// unwind convert the array to objects to apply filter on it
{
$unwind:{
preserveNullAndEmptyArrays : true, // this remove the object which is null
path : "$user"
}
},
// in match you have to define your condition this check if the user has role equals to 3
{
$match:{
"user.role": 3
}
},
{
// this provide pagination
$facet: {
edges: [
{ $skip: sk },
{ $limit: lm },
],
/* pageInfo: [
{ $group: { _id: null, count: { $sum: 1 } } },
],*/
},
}
], function (err, result) {
if (err) {
// this res is send if there is some error
console.log(err);
callback(500, err)
} else {
// you get the data
console.log(result);
callback(200, result)
}
});
Look if this can helps you, I need more information about the problem, that's a possible solution.
BookModel.find()
.populate({
path: 'author',
match: { author: 'author1' },
select: 'name' // I'm suppossing you want to select "name" field, if not, delete this line.
}).find(function(err,books){
console.log(books);
});
or you can do this:
var query = BookModel.find({name: 'author1'});
query.populate('author','name');
query.find(function(err,books){
console.log(books);
});
You could add an extra query in your results to filter out the nulls:
var query = BookModel.find();
query.populate('author','name',{name:'author1'});
query.find(function(err, books){
books = books.filter(function(b){ return b.author; });
console.log(books, null, 4);
});
Console output
[
{
"author": {
"_id": "4ea0db52e09aa6aad2e831fe",
"name": "author1"
},
"title": "book1",
"_id": "4ea0dbebc191848704000005"
}
]

How to group records by a field in Mongoose?

I've got a MongoDB document which has records that looks like this:
[
{ _id: id, category_name: 'category1', parent_category: null },
{ _id: id, category_name: 'category2', parent_category: null },
{ _id: id, category_name: 'subcategory1', parent_category: id_parent_1 },
{ _id: id, category_name: 'subcategory2', parent_category: id_parent_1 },
{ _id: id, category_name: 'subcategory3', parent_category: id_parent_2 },
{ _id: id, category_name: 'subcategory4', parent_category: id_parent_2 }
]
As you can see, I'm storing categories with a parent_category of null, and subcategories have the parent category's ID. What I'm looking for is to group these into some kind of format like this:
[
{ category_name: 'category1', categories: [
{ category_name: 'subcategory1', _id: id },
{ category_name: 'subcategory2', _id: id }
]
},
{ category_name: 'category2', categories: [
{ category_name: 'subcategory3', _id: id },
{ category_name: 'subcategory4', _id: id }
]
}
]
So basically group the parent categories with an array with their child categories. I'm using Mongoose. I tried using the aggregation framework MongoDB provides but I can't get the desired result. :(
I have access to modify the schema in any way that could be needed!
Thanks in advance!
It seems like you're treating Mongo like an relational database (separating all these fields and bringing them together with a query). What you should do is rebuild your Schema. For example:
var CategorySchema = new Schema({
category_name: String,
subCategories:[subCategorySchema]
}
var subCategorySchema = new Schema({
category_name: String
})
This way when you need to query the collection it's a simple
db.find({category_name: "name of the category"}, function(){})
to get everything you need.
Just in case: you can add the sub categories to the array with simple updates. Read this for more info.
Please try this if Your schema is not changed:
var MongoClient = require('mongodb').MongoClient
//connect away
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
if (err) throw err;
console.log("Connected to Database");
//simple json record
var document = [];
//insert record
//db.data.find({"parent_category":null }).forEach(function(data) {print("user: " + db.data.findOne({"parent_category":data._id })) })
db.collection('data').find({"parent_category":null }, function(err, parentrecords) {
if (err) throw err;
var cat ={};
parentrecords.forEach(function(data){
cat["category_name"] = data["category_name"];
db.collection('data').find({"parent_category":data._id },function(err, childrecords) {
var doc = [];
childrecords.forEach(function(childdata){
doc.push(childdata);
},function(){
cat["categories"] = doc;
document.push(cat);
console.log(document);
});
});
});
});
});
If you want to find out expected results without changing schema then you basically follow some complex mongo aggregation query. For finding output I follow following steps :
First in $project check parent_category equals null if true then add _id else add parent_category.
Now document structure looks like with new key name as parent_id presents and group by parent_id and push remaining data like category_name and parent_category.
After that use $setDifference and $setIntersection to differentiate parent data and child data.
And in finally unwind only single array objects so this single array object and used project for showing only those fields which to display.
Check working aggregation query as below :
db.collectionName.aggregate({
"$project": {
"parent_id": {
"$cond": {
"if": {
"$eq": ["$parent_category", null]
},
"then": "$_id",
"else": "$parent_category"
}
},
"category_name": 1,
"parent_category": 1
}
}, {
"$group": {
"_id": "$parent_id",
"categories": {
"$push": {
"category_name": "$category_name",
"parent_category": "$parent_category"
}
},
"parentData": {
"$push": {
"$cond": {
"if": {
"$eq": ["$parent_category", null]
},
"then": {
"category_name": "$category_name",
"parent_category": "$parent_category"
},
"else": null
}
}
}
}
}, {
"$project": {
"findParent": {
"$setIntersection": ["$categories", "$parentData"]
},
"categories": {
"$setDifference": ["$categories", "$parentData"]
}
}
}, {
"$unwind": "$findParent"
}, {
"$project": {
"category_name": "$findParent.category_name",
"categories": 1
}
}).pretty()
In order to group records by a field try using $group in Aggreegation.This worked for me.
Example
db.categories.aggregate(
[
{ $group : { _id : {category_name:"$category_name"}, categories: { $push: {category_name:"$category_name",_id:"$_id"} } } }
]
)
Reference:
MongoDB Aggregation
Hope this works.

Categories