NodeJs MongoDb Mongoose - How to Delete item from Multi Dimensional Array? - node.js

//MONGOOSE SCHEMA OBJECT
var userSchema = new Schema( {
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
tags:[ {name : String, color : String } ],
bookmarks:[{link : String, tags:[ {name : String, color : String } ]}]
} );
module.exports = userSchema; //Export the userSchema
var UserModel = mongoose.model('UserModel', userSchema ); //Create Model
module.exports = UserModel; //Export the Model
//I CAN DELETE AN ITEM FROM BOOKMARKS ARRAY NO PROBLEM USING
UserModel.findByIdAndUpdate(userId ,{$push : {bookmarks: {link : req.body.link, tags : req.body.tags}}}, function(err, user_data) {
PROBLEM!!
How do I delete a tag from the tags Array, within the bookmarks array given users _id, bookmarks _id and the tag _id or name?
//I have tried variations of the following without success
var update = {bookmarks:[{ _id : bookmarkId},
$pull: {tags:[_id : tagid ] }] };
UserModel.findByIdAndUpdate(userId ,update, function(err, user_data) {
AND
UserModel.findOne( {_id : userId}).select({ bookmarks:[ { $elemMatch: {_id : req.params.bookmarkId}}] }).exec(function(err, user_data)
Initially I was using different Models and subdocuments.
var bookmarkSchema = new Schema( {
link : String,
tags:[tagSchema]
});
var tagSchema = new Schema( {
name : String,
color : String
});
var userSchema = new Schema( {
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
tags:[ {name : String, color : String } ],
bookmarks: [bookmarkSchema]
} );
However, I was unable to delete items from the subdocuments using the $pull command like I used above. So I reverted to just using one schema/model.
This is a very important task to be able to complete and I would be greatful for help.
Thanks Muhammad but I could not get either of the 2 methods to work:
1) Nothing happens to DB and the values of the callbacks are:
*numberAffected: 0
*raw: {"updatedExisting":false,"n":0,"connectionId":322,"err":null,"ok":1}
UserModel.update({bookmarks:req.params.bookmarkId},
{ $pull: {"bookmarks.tags" :{_id:req.body._id, name :req.body.name ,color:req.body.color}}}, function(err, numberAffected, raw) {
2) I had to use the lean() function to convert Mongoose Document data format to normal JSON. Otherwise bookmarks.push({link:user.bookmarks[i].link,_id:user.bookmarks[i]._id,tags:tags})
would not combine properly with:
bookmarks.push(user.bookmarks[i]);
on bookmarks[]
However using the lean() functions means I would not be able to save the data to the DB with .save
UserModel.findById(userId).lean(true).exec(function(err, user) {

delete from sub-Document of JSON doc,
UserModel.update({bookmarks:bookmarkId},{$pull: {"bookmarks.tags" :{_id:tagsId,name :name ,color:color}}}, function(err, user_data) {
or
UserModel.findOnd(userId,function(er,user){
var bookmarks= [];
var tags = [];
for (var i = 0;i<user.bookmarks.length;i++){
if (user.bookmarks[i]._id==bookmarkId)
{
for (var j = 0;j<user.bookmarks[i].tags.length;j++){
if (user.bookmarks[i].tags[j]._id==tagid )
{
}else {
tags.push(user.bookmarks[i].tags[j])
}
}
bookmarks.push({link:user.bookmarks[i].link,_id:user.bookmarks[i]._id,tags:tags})
}
else {
bookmarks.push(user.bookmarks[i]);
}
}
})

Related

How can i send a query to Push details to my MongoDB using Node and mongoose [duplicate]

Basically I have a mongodb collection called 'people'
whose schema is as follows:
people: {
name: String,
friends: [{firstName: String, lastName: String}]
}
Now, I have a very basic express application that connects to the database and successfully creates 'people' with an empty friends array.
In a secondary place in the application, a form is in place to add friends. The form takes in firstName and lastName and then POSTs with the name field also for reference to the proper people object.
What I'm having a hard time doing is creating a new friend object and then "pushing" it into the friends array.
I know that when I do this via the mongo console I use the update function with $push as my second argument after the lookup criteria, but I can't seem to find the appropriate way to get mongoose to do this.
db.people.update({name: "John"}, {$push: {friends: {firstName: "Harry", lastName: "Potter"}}});
Assuming, var friend = { firstName: 'Harry', lastName: 'Potter' };
There are two options you have:
Update the model in-memory, and save (plain javascript array.push):
person.friends.push(friend);
person.save(done);
or
PersonModel.update(
{ _id: person._id },
{ $push: { friends: friend } },
done
);
I always try and go for the first option when possible, because it'll respect more of the benefits that mongoose gives you (hooks, validation, etc.).
However, if you are doing lots of concurrent writes, you will hit race conditions where you'll end up with nasty version errors to stop you from replacing the entire model each time and losing the previous friend you added. So only go to the latter when it's absolutely necessary.
The $push operator appends a specified value to an array.
{ $push: { <field1>: <value1>, ... } }
$push adds the array field with the value as its element.
Above answer fulfils all the requirements, but I got it working by doing the following
var objFriends = { fname:"fname",lname:"lname",surname:"surname" };
People.findOneAndUpdate(
{ _id: req.body.id },
{ $push: { friends: objFriends } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
)
Another way to push items into array using Mongoose is- $addToSet, if you want only unique items to be pushed into array. $push operator simply adds the object to array whether or not the object is already present, while $addToSet does that only if the object is not present in the array so as not to incorporate duplicacy.
PersonModel.update(
{ _id: person._id },
{ $addToSet: { friends: friend } }
);
This will look for the object you are adding to array. If found, does nothing. If not, adds it to the array.
References:
$addToSet
MongooseArray.prototype.addToSet()
Use $push to update document and insert new value inside an array.
find:
db.getCollection('noti').find({})
result for find:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
update:
db.getCollection('noti').findOneAndUpdate(
{ _id: ObjectId("5bc061f05a4c0511a9252e88") },
{ $push: {
graph: {
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
}
})
result for update:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
},
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
First I tried this code
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
}
);
But I noticed that only first friend (i.e. Johhny Johnson) gets saved and the objective to push array element in existing array of "friends" doesn't seem to work as when I run the code , in database in only shows "First friend" and "friends" array has only one element !
So the simple solution is written below
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
{ upsert: true }
);
Adding "{ upsert: true }" solved problem in my case and once code is saved and I run it , I see that "friends" array now has 2 elements !
The upsert = true option creates the object if it doesn't exist. default is set to false.
if it doesn't work use below snippet
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
).exec();
An easy way to do that is to use the following:
var John = people.findOne({name: "John"});
John.friends.push({firstName: "Harry", lastName: "Potter"});
John.save();
In my case, I did this
const eventId = event.id;
User.findByIdAndUpdate(id, { $push: { createdEvents: eventId } }).exec();
Push to nested field - use a dot notation
For anyone wondering how to push to a nested field when you have for example this Schema.
const UserModel = new mongoose.schema({
friends: {
bestFriends: [{ firstName: String, lastName: String }],
otherFriends: [{ firstName: String, lastName: String }]
}
});
You just use a dot notation, like this:
const updatedUser = await UserModel.update({_id: args._id}, {
$push: {
"friends.bestFriends": {firstName: "Ima", lastName: "Weiner"}
}
});
This is how you could push an item - official docs
const schema = Schema({ nums: [Number] });
const Model = mongoose.model('Test', schema);
const doc = await Model.create({ nums: [3, 4] });
doc.nums.push(5); // Add 5 to the end of the array
await doc.save();
// You can also pass an object with `$each` as the
// first parameter to use MongoDB's `$position`
doc.nums.push({
$each: [1, 2],
$position: 0
});
doc.nums;
// This is the my solution for this question.
// I want to add new object in worKingHours(array of objects) -->
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
// employeeRoutes.js
const express = require("express");
const router = express.Router();
const EmployeeController = require("../controllers/employeeController");
router
.route("/:id")
.put(EmployeeController.updateWorkingDay)
// employeeModel.js
const mongoose = require("mongoose");
const validator = require("validator");
const employeeSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Please enter your name"],
},
address: {
type: String,
required: [true, "Please enter your name"],
},
email: {
type: String,
unique: true,
lowercase: true,
required: [true, "Please enter your name"],
validate: [validator.isEmail, "Please provide a valid email"],
},
phone: {
type: String,
required: [true, "Please enter your name"],
},
joiningDate: {
type: Date,
required: [true, "Please Enter your joining date"],
},
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const Employee = mongoose.model("Employee", employeeSchema);
module.exports = Employee;
// employeeContoller.js
/////////////////////////// SOLUTION IS BELOW ///////////////////////////////
// This is for adding another day, entry and exit time
exports.updateWorkingDay = async (req, res) => {
const doc = await Employee.findByIdAndUpdate(req.params.id, {
$push: {
workingHours: req.body,
},
});
res.status(200).json({
status: "true",
data: { doc },
});
};
https://www.youtube.com/watch?v=gtUPPO8Re98
I ran into this issue as well. My fix was to create a child schema. See below for an example for your models.
---- Person model
const mongoose = require('mongoose');
const SingleFriend = require('./SingleFriend');
const Schema = mongoose.Schema;
const productSchema = new Schema({
friends : [SingleFriend.schema]
});
module.exports = mongoose.model('Person', personSchema);
***Important: SingleFriend.schema -> make sure to use lowercase for schema
--- Child schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SingleFriendSchema = new Schema({
Name: String
});
module.exports = mongoose.model('SingleFriend', SingleFriendSchema);

mongoose query with $in property

var mongoose = require('mongoose');
var FriendSchema = new mongoose.Schema({
requester: {
type: String,
required: true,
},
recipient: {
type: String,
required: true,
},
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var Friend = mongoose.model('Friend', FriendSchema);
module.exports = Friend;
I am trying to query it by using
Friend.find({"requester": { $in: [some id value]}}, function(err, fee){
console.log(fee.recipient);
});
and having it return the recipient id value..
Any suggestions would really be helpful, thank you.
you can use projection of mongo.db
the structure is like below ,
find(condition , requirefield , callback);
below is the example in which the fee will contain only the recipient
Friend.find({"requester": { $in: [some id value]}}, { _id : 0 , requester : 0 } ,function(err, fee){
console.log(fee.recipient);
});
you can refer https://docs.mongodb.com/manual/reference/method/db.collection.find/#projections for more reference.

Is it possible to query subdocuments directly using mongoose?

let's say there was a User model and a Post model. In this situation User's would have many posts; User would be the parent and Post would be the child. Is it possible to query for posts directly?
For instance if I wanted to do something like
app.get('/post/search/:query', (req,res) => {
Posts.find({title: req.params.query }, (err,post) => {
res.send(JSON.stringify(post))
})
})
or would one have to do:
app.get('/post/search/:query',(req,res) => {
let resultsFromQuery = [];
User.find({'post.title':req.params.query'}, (err,user) => {
user.posts.forEach((post) => {
if(post.title === req.params.query){
resultsFromQuery.push(post);
}
})
})
res.send(JSON.stringify(resultsFromQuery))
})
EDIT: Here is my schema's.
User Schema (Parent)
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
PostSchema = require('./post.js');
let UserSchema = new Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [PostSchema]
})
module.exports = mongoose.model('User',UserSchema);
Post Schema (Child)
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
let PostSchema = new Schema({
title: {
type: String
},
description: {
type: String
},
image: {
type: String
},
original_poster: {
id: {
type: String,
required: true
},
username: {
type: String,
required: true
}
},
tags: {
type: [String],
required: true
}
})
module.exports = PostSchema;
EDIT:
Here is a sample document
the result of db.users.find({username: 'john'})
{
"_id" : ObjectId("5a163317bf92864245250cf4"),
"username" : "john",
"password" : "$2a$10$mvE.UNgvBZgOURAv28xyA.UdlJi4Zj9IX.OIiOCdp/HC.Cpkuq.ru",
"posts" : [
{
"_id" : ObjectId("5a17c32d54d6ef4987ea275b"),
"title" : "Dogs are cool",
"description" : "I like huskies",
"image" : "https://media1.giphy.com/media/EvRj5lfd8ctUY/giphy.gif",
"original_poster" : {
"id" : "5a163317bf92864245250cf4",
"username" : "john"
},
"tags" : [
"puppies",
"dogs"
]
}
],
"__v" : 1
}
Yes you can find directly the post title from the user model. like bellow
User.find({"posts.title": "Cats are cool"}, (err, users) => {
if(err) {
// return error
}
return res.send(users)
})
That will return user with all post not only the matching post title. So to return only matching post title can use $ positional operator. like this query
User.find({"posts.title": "Cats are cool"},
{username: 1, "posts.$": 1}, // add that you need to project
(err, users) => {
if(err) {
// return error
}
return res.send(users)
})
that only return matching post
Since you are saving OP data, why not do:
// you'll need to adapt how your are getting the user-id here
const { user } = req
Post.find({ title: 'the title', 'original_poster.id': user.id }, (err, posts) => {
console.log(posts); })
Though I would advise you to adjust your Post-schema:
original_poster: {
type: Schema.Types.ObjectId,
ref: 'User'
}
},
Then you can do Post.find({}).populate('original_poster') to include it in your results.!

Mongoose data saving without _id

I am using mongoose with node.js application. I don't want _id field in record.
I am using this code to save my record without _id field. But it is giving error
document must have an _id before saving
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PlayerSchema = new Schema({
player_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String }
}
, { _id: false }
);
var Player = mongoose.model('Player', PlayerSchema );
var athlete = new Player();
athlete.player_id = 1;
athlete.player_name = "Vicks";
athlete.player_age = 20;
athlete.player_country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});
I am using mongoose version 3.8.14
Unfortunately, You can not skip having a primary key for the document but you can override the primary key content, you can define your own primary key for each document.
Try the following schema for the same.
var PlayerSchema = new mongoose.Schema({
_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String },
}
);
I have replaced your player_id with _id. Now you have control over the primary key of the document and the system won't generate the key for you.
There are some plugins which can also do the autoincremet for your primary key. https://github.com/chevex-archived/mongoose-auto-increment. You might try these as well.
Also, about the error you are getting :
Any document is an object and should be wrapped inside the curly brackets you can not define two independent object in the same document. So you are getting this error.
Copying an answer given on a similar Github issue comment:
The _id option exists to prevent one being created and assigned to
every sub-document within your document.
So, it's not for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String
}]
}, {_id: false});
// ^^^^^^^^^
// this won't work
It's for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String,
_id: false // <--- this works
}]
});
Took me a while but, after running into the same problem, I found the
documentation here.
instade of writing this
{
player_id : { type: Number },
player_name : { type: String },
...}
write this:-
{
_id : { type: Number },
_name : { type: String },
_age : { type: Number },
_country : { type: String }
}
hence _id will be players_id
then further write code like this:-
var athlete = new Player();
athlete._id = 1;
athlete._name = "Vicks";
athlete._age = 20;
athlete._country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});

Mongoose.js: Atomic update of nested properties?

Using Mongoose version 3.6.4
Say I have a MongoDB document like so:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joe",
"last" : "Pesci",
"middle" : "Frank"
}
}
}
And I have the following schema for Users:
var UserSchema = new mongoose.Schema({
_id: { type: String },
email: { type: String, required: true, index: { unique: true }},
active: { type: Boolean, required: true, 'default': false },
profile: {
name: {
first: { type: String, required: true },
last: { type: String, required: true },
middle: { type: String }
}
}
created: { type: Date, required: true, 'default': Date.now},
updated: { type: Date, required: true, 'default': Date.now}
);
And I submit a form passing a field named: profile[name][first] with a value of Joseph
and thus I want to update just the user's first name, but leave his last and middle alone, I thought I would just do:
User.update({email: "joe#foo.com"}, req.body, function(err, result){});
But when I do that, it "deletes" the profile.name.last and profile.name.middle properties and I end up with a doc that looks like:
{
"_id" : "5187b74e66ee9af96c39d3d6",
"profile" : {
"name" : {
"first" : "Joseph"
}
}
}
So it's basically overwriting all of profile with req.body.profile, which I guess makes sense. Is there any way around it without having to be more explicit by specifying my fields in the update query instead of req.body?
You are correct, Mongoose converts updates to $set for you. But this doesn't solve your issue. Try it out in the mongodb shell and you'll see the same behavior.
Instead, to update a single deeply nested property you need to specify the full path to the deep property in the $set.
User.update({ email: 'joe#foo.com' }, { 'profile.name.first': 'Joseph' }, callback)
One very easy way to solve this with Moongose 4.1 and the flat package:
var flat = require('flat'),
Schema = mongoose.Schema,
schema = new Schema(
{
name: {
first: {
type: String,
trim: true
},
last: {
type: String,
trim: true
}
}
}
);
schema.pre('findOneAndUpdate', function () {
this._update = flat(this._update);
});
mongoose.model('User', schema);
req.body (for example) can now be:
{
name: {
first: 'updatedFirstName'
}
}
The object will be flattened before the actual query is executed, thus $set will update only the expected properties instead of the entire name object.
I think you are looking for $set
http://docs.mongodb.org/manual/reference/operator/set/
User.update({email: "joe#foo.com"}, { $set : req.body}, function(err, result){});
Try that
Maybe it's a good solution - add option to Model.update, that replace nested objects like:
{field1: 1, fields2: {a: 1, b:2 }} => {'field1': 1, 'field2.a': 1, 'field2.b': 2}
nestedToDotNotation: function(obj, keyPrefix) {
var result;
if (keyPrefix == null) {
keyPrefix = '';
}
result = {};
_.each(obj, function(value, key) {
var nestedObj, result_key;
result_key = keyPrefix + key;
if (!_.isArray(value) && _.isObject(value)) {
result_key += '.';
nestedObj = module.exports.nestedToDotNotation(value, result_key);
return _.extend(result, nestedObj);
} else {
return result[result_key] = value;
}
});
return result;
}
});
need improvements circular reference handling, but this is really useful when working with nested objects
I'm using underscore.js here, but these functions easily can be replaced with other analogs

Resources