Cannot pull from mongoose array - node.js

In my schema I have two arrays with users, invitedUsers and joinedUsers. Invited array is created from request body
invitedUsers: req.body.invitedUsers
Joined array is updated when user clicks 'Join'
$addToSet: {joinedUsers: req.user}
I can then pull user from joinedUsers array by performing
{$pull: {joinedUsers: {_id: req.user._id}}}
However, I can't pull from invited users array, although user object is the same. Here is the complete code:
app.put('/decline/events/:id', function (req, res) {
var update = {$pull: {invitedUsers: {_id: req.user._id}}};
console.log(req.user._id);
Event.findByIdAndUpdate(req.params.id, update, function (err, event) {
if (err) {
res.send(err);
};
Event.find({})
.populate('owner')
.exec(function (err, events) {
if (err) {
res.send(err);
};
res.json(events)
});
});
});
Ajax call on client
$scope.declineInvitation = function (id) {
$http.put('/decline/events/' + id)
.success(function (data) {
})
.error(function (data) {
console.log('Error: ' + data);
});
};
Everything goes without errors, but invitedArray doesn't change, as if mongoose fails to find it. What might be a solution to this?
Update
Event Schema definition, as requested:
var eventSchema = new Schema({
title: {type: String},
description: {type: String, default: ''},
startDate: Date,
invitedUsers: {type: Array, default: []},
joinedUsers: {type: Array, default: []},
owner: {type: Schema.Types.ObjectId, ref: 'User'},
created_at: {type: Date, default: Date.now()},
updated_at: {type: Date, default: null},
location: String
}

The problem was because the _id in invitedUsers is a String and req.user._id is an ObjectId.
Calling toString() on _id will solve it:
var update = {$pull: {invitedUsers: {_id: req.user._id.toString()}}};

Related

Mongoose nested schemas

I'm creating an app where you log workouts and I'm having some problems with Mongoose.
I have two schemas, one for workouts and one for exercises. When the user adds a new exercise, I want it to be stored inside the workout, and I've been trying this in a bunch of ways.
For now, the exercises are saved in a different collection in my MongoDB (don't know if this is the best way to do it), and I thought that it should save the exercise inside the workout.exercises, but there is only the objectID. How do I resolve this? Have looked at the populate function, but can't figure out how to get it to work.
addExercises
export function addExercise(req, res) {
if (!req.body.exercise.title) {
res.status(403).end();
}
const newExercise = new Exercise(req.body.exercise);
// Let's sanitize inputs
newExercise.title = sanitizeHtml(newExercise.title);
newExercise.cuid = cuid();
newExercise.sets = [];
newExercise.save((err, saved) => {
if (err) res.status(500).send(err);
});
Workout
.findOneAndUpdate(
{cuid: req.body.exercise.workoutCUID},
{$push: {exercises: newExercise}},
{upsert: true, new: true},
function (err, data) {
if (err) console.log(err);
});
}
getExercises
export function getExercises(req, res) {
Workout.findOne({cuid: req.params.cuid}).exec((err, workout) => {
if (err) {
res.status(500).send(err);
}
console.log(workout);
let exercises = workout.exercises;
res.json({exercises});
});
}
Workout
import mongoose from "mongoose";
const Schema = mongoose.Schema;
var Exercise = require('./exercise');
const workoutSchema = new Schema({
title: {type: 'String', required: true},
cuid: {type: 'String', required: true},
slug: {type: 'String', required: true},
userID: {type: 'String', required: true},
exercises: [{ type: Schema.Types.ObjectId, ref: 'Exercise' }],
date: {type: 'Date', default: Date.now, required: true},
});
export default mongoose.model('Workout', workoutSchema);
Exercise
import mongoose from "mongoose";
const Schema = mongoose.Schema;
var Workout = require('./workout');
const exerciseSchema = new Schema({
title: {type: 'String', required: true},
cuid: {type: 'String', required: true},
workoutCUID: {type: 'String', required: true},
sets: {type: 'Array', "default": [], required: true}
});
export default mongoose.model('Exercise', exerciseSchema);
Based on your Workout schema, you declare the type of the Exercises field to be [{ type: Schema.Types.ObjectId, ref: 'Exercise' }]. This means that this field should be an array of Mongoose ObjectId's.
It appears that you are attempting to add the whole exercise object to the workout's exercises field, rather than just the ObjectId. Try modifying it this way:
const newExercise = new Exercise(req.body.exercise);
// Let's sanitize inputs
newExercise.title = sanitizeHtml(newExercise.title);
newExercise.cuid = cuid();
newExercise.sets = [];
newExercise.save((err, saved) => {
if (err) res.status(500).send(err);
// Nest the Workout update in here to ensure that the new exercise saved correctly before proceeding
Workout
.findOneAndUpdate(
{cuid: req.body.exercise.workoutCUID},
// push just the _id, not the whole object
{$push: {exercises: newExercise._id}},
{upsert: true, new: true},
function (err, data) {
if (err) console.log(err);
});
});
Now that you correctly have the ObjectId saved in the exercises field, .populate should work when you query the workout:
Workout.findById(id).populate("exercises").exec((err, workout) => {
// handle error and do stuff with the workout
})
Workout.findById(req.params.id).populate("exercises").exec((err,workout) =>{
res.status(200).json(workout);
})
It should work this way

Moongoose schema usage for update

Here is my schema
var DrivingSchema = new Schema({
title: { type: String, required: true },
permalink: {type: String, required: true},
phone: {type: Number, required: true},
mobile: {type: Number},
bike: {type: Boolean, default: false }
});
I used this schema for adding data. It worked fine.
But when I have to update data, I couldn't use this schema because it gave new _id. Here is my controller for update.
DriveModel.findOne({permalink: permalink}, function(err, data) {
if (err)
res.send(err);
var newData = new DriveModel({
title: title,
phone: phone,
mobile: mobile,
bike: bike});
DriveModel.update({_id:data._id}, newData, function(err, result) {
if (err)
res.send(err);
else{res.redirect('/centres/'+permalink);}
});
});
This controller didn't work because of _id conflict. Mongoose Schema documentation suggests to use _id: false in schema but it again works for update not for new insertion of data. Now, how could I solve this issue? Do I have to build another schema just for update or is there anyway to handle with same schema?
Try this one:
var elements = {"title": title, "phone": phone, "mobile": mobile, "bike": bike};
DriveModel.findOne({"permalink": permalink}, function(err, data) {
if (err) {
res.end(err);
}
for(elem in elements) {
data[elem] = elements[elem];
}
data.save(function(err, place) {
if(err) {
res.end(err);
} else {
res.redirect('/centres/'+permalink);
}
});
});

Mongoose Subdocument array pushing

My scenario is if person1 accepting person2 deal means..the person1_id will save inside that person2 particular deal field accepted,i have tried the code it was working perfectly if a accepted user(person2) has one deal but in case of more than one deal it was updating but deleting other deals (i.e,the suppose the person2 having 3 deals means if person1 accepting 3rd deal the accepted user id was updating in 3rd deal and the 1st and 2nd deal was deleted).Anyone please help me how to save only the updated deal array
var incomingUser = req.user;//accepting user accesstoken in header(person1)
if(req.params.id){
var id = req.params.id;//deal id
console.log("DealId:"+id + "Acceptinguser:"+incomingUser.name);
User.findOne(
{
"deals": {
$elemMatch: {
_id: id
}
}
},
function(err, data){
console.log("Dealer:" +data.name);
console.log("deal:"+ data.deals);
if(err){
console.log("User not found");
res.send(new restify.ResourceNotFoundError('failed','Deal not found'));
return next();
}
var dealObj = _.filter(data.deals, { id: id })[0];
console.log("Deal Obj" + dealObj);
var acceptingUser = incomingUser;
console.log("accepting user:" +acceptingUser._id);
dealObj.accepted = acceptingUser._id;
console.log("accept id: "+ dealObj.accepted);
data.deals = dealObj;
console.log("data:"+ data.deals);
data.save(function (err, result){
console.log("Result:" + result);
if(err){
console.log("Internal error");
res.send(new restifyc.InternalError('failed','Error accepting'));
return next();
}
console.log("saved");
res.send(200,{user: result});
return next();
});
});
}
}
And my schema is
var dealSchema = new mongoose.Schema({
shopName: {type: String,required: true},
deal: {type: String,required: true},
price:{type: Number,required: true},
start:{type: Date,default: Date.now},
end:{type: Date},
expiry:{type: Date},
comments:{type: String},
accepted: {type:mongoose.Schema.Types.ObjectId, ref:'user'},//person1 _id
rejected: {type:mongoose.Schema.Types.ObjectId, ref: 'user'}
});
var userSchema = new mongoose.Schema({
name: { type: String,required: true},
phone: { type: Number, required: true,unique: true},
email:{type: String},
password: {type: String},
deals:[dealSchema]
}, {collection: 'user'});
mongoose.model('Deal', dealSchema);
mongoose.model('user', userSchema);
Yep in order to update specifically what you need you can use the <array>.$ for the specified position of the element:
User.update(
"deals": {
$elemMatch: {
_id: id
}
}, {
"$set": {
"deals.$" : {/*your deal data*/}
}
}, function(err, doc) {
});
More details on how to use the $ wildcard https://docs.mongodb.org/manual/reference/operator/update/positional/

mongodb populate method not working

Here is my code for models
var postSchema = new mongoose.Schema({
created_by: {type: Schema.ObjectId, ref:'User', autopopulate: true }, //should be changed to ObjectId, ref "User"
created_at: {type: Date, default: Date.now},
text: String
});
var userSchema = new mongoose.Schema({
username: String,
password: String, //hash created from password
created_at: {type: Date, default: Date.now}
});
Below is the code of how i insert data and try to retrieve it using populate method.
Post.create({text: 'farheen123',created_by: '5587bb520462367a17f242d2'}, function(err, post){
if(err) console.log("Farheen has got error"+err);
else console.log(post);
});
//5587f5556e6f2b38244d02d1: _id of already created user
Post
.findOne({ _id: '5587f5556e6f2b38244d02d1' })
.populate('created_by')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story);
// prints "The creator is Aaron"
});
The result that i get is below. It gives numm reference to created_by, instead of giving username and password of that id.
The creator is { _id: 5587f5556e6f2b38244d02d1,
text: 'farheen123',
created_by: null,
__v: 0,
created_at: Mon Jun 22 2015 17:15:25 GMT+0530 (IST) }
When you create an instance of the Post model, you need to assign the _id from the user as an ObjectId, not a string:
var ObjectId = require('mongoose').Types.ObjectId;
Post.create({
text: 'farheen123',
created_by: new ObjectId('5587bb520462367a17f242d2')
}, function(err, post) {
if(err) console.log("Farheen has got error"+err);
else console.log(post);
});

Mongoose - Better solution with appending additional information

I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.

Resources