Update single sub-document attribute with Mongoose - node.js

I have a Mongo Collection with an array of sub-documents, like so:
var aliasSchema = new Schema({
name: String,
alias_type: String,
isCommonName: { type: Boolean, default: false }
});
var parentSchema = new Schema(
{
name: String,
description: { type: String, required: false },
observation: { type: String, required: false },
indexed: { type: Boolean, default: true },
aliases: [aliasSchema],
}
I wanted to update a single alias object's alias_type.
I have tried using two approaches with Mongoose
1.
Parent.findOneAndUpdate({'aliases.name': aliasName },{$set: {"aliases.$.alias_type": req.body.aliasForm.alias_type}}, {new: true}, function(err, aliasDoc) {
if (err) {
res.status(400)
res.send(err);
}
console.log(aliasDoc);
res.status(200);
res.send(aliasDoc);
});
and
2.
Parent.update({'aliases.name': aliasName },{$set: {"aliases.$.alias_type": req.body.aliasForm.alias_type}}, function(err, aliasDoc) {
if (err) {
res.status(400)
res.send(err);
}
console.log(aliasDoc);
res.status(200);
res.send(aliasDoc);
});
However, I am still unsuccessful in updating the alias_type.
When I ran option (1) in the mongo console like so, it worked:
db.getCollection('parent').findOneAndUpdate({'aliases.name': 'SomeValue' },{$set:{'aliases.$.alias_type': 'AnotherValue'}})
Can anyone tell me what I am doing wrong?

I was making a mistake while calling one of the parameters. The update() technique that I posted in the question worked :)

Related

Getting error about $pushAll when going to endpoint, but not using it anywhere

I have a strange one...
I've developed an api with Node/Express/Mongoose using Mongodb 3.4.9, now it's 3.4.17.
I have no ideal why, but for some reason a block of code I have been using for ages is throwing an error:
{name: "MongoError", message: "Unknown modifier: $pushAll", driver: true, index: 0, code: 9,…}
code: 9
driver: true
errmsg: "Unknown modifier: $pushAll"
index: 0
message: "Unknown modifier: $pushAll"
name: "MongoError"
Here is the code:
router.route('/addemail/:id')
// ADD EMAILS
.put(function(req, res){
Profile.findOne({'owner_id':req.params.id}, function(err, profile){
if(err)
res.send(err);
profile.emails.push({
email_type: req.body.email_type,
email_address: req.body.email_address
})
profile.save(function(err){
if(err)
res.send(err);
res.json(profile);
});
});
});
As you can see, I'm not using $pushAll in this block of code, or actually anywhere in my code.
What else could be causing this???
Thanks for any guru advise.
Update: Here is my model for the profile and I'm including the emails model next:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// SUBDOCUMENTS
var AddressesSchema = require('./profile/addresses');
var BusinessesSchema = require('./profile/businesses');
var EmailsSchema = require('./profile/emails');
var PhonesSchema = require('./profile/phones');
var SocialSchema = require('./profile/social');
// PROFILE (PARENT) MODEL
var ProfileSchema = new Schema({
//PROFILE INFO
owner_id: {
type: String,
require: true,
unique: true
},
notice: {
type: Number, // 1=profile, 2=profile and cards
},
first_name:{
type: String
},
last_name:{
type: String
},
initial:{
type: String
},
birthday:{
type: Date
},
highschool:{
type: String
},
college:{
type: String
},
facebook:{
type: String
},
linkedin:{
type: String
},
linkedin_bus:{
type: String
},
twitter: {
type: String
},
google: {
type: String
},
pinterest: {
type: String
},
user_image: {
type: String
},
contacts:[{
type:Schema.Types.ObjectId,
ref:'Contact'
}],
//SUBDOCUMENTS
emails:[EmailsSchema],
phones:[PhonesSchema],
addresses:[AddressesSchema],
businesses:[BusinessesSchema],
social:[SocialSchema]
});
module.exports = mongoose.model('Profile', ProfileSchema);
Here is what the emails model looks like:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// CONTACT (PARENT) MODEL
var EmailSchema = new Schema({
//CONTACT INFO
email: {
type: String,
require: true
},
date_registered: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Email', EmailSchema);
Mongoose probably creates a $pushAll under the hood which, however, has been removed in newer version of MongoDB as you can see here. So this is why you get the error.
I suggest you upgrade to the latest version of Mongoose which will fix this.
Also see these discussions on the Mongoose repo: https://github.com/Automattic/mongoose/issues/4455
https://github.com/Automattic/mongoose/issues/5574
Pardon me for asking but why don't you just:
// ADD EMAILS
.put(function(req, res) {
Profile.update({'owner_id': req.params.id},
{
$addToSet: {
email_type: req.body.email_type,
email_address: req.body.email_address
}
});
});
It seems you just want to add an object to an array in a mongo document based on owner_id. $addToSet does that.
You should get advantage of some mongodb nice features i.e. you could do these:
Profile.findOneAndUpdate({'owner_id':req.params.id},{addToSet:{emails:[ email_type: req.body.email_type, email_address: req.body.email_address]}}, function(err, profile){
if(err){
res.send(err);
} else {
res.json(profile);
}
}

Mongoose preventing saving two documents and sub documents

I'm running into an issue using Mongoose, Express where I want to save a sub document to my user by pushing it into the sub document array, which I can do. However the issues arise when I want to delete a gamesession that is stored in the users "sessions" attribute and also delete the gamesession globally. I think the issue arises because I'm saving two seperate instances of a gamesession. Here is the code for creating a new sub document called "gamesession" and pushing it onto the users "session" attribute
//POST /posts
// Route for creating gamesessions for specific user
router.post("/gamesessions/:uID/", function(req, res, next) {
var gamesession = new GameSession(req.body);
req.user.sessions.push(gamesession);
gamesession.postedBy = req.user._id;
req.user.save(function(err, user) {
if(err) return next(err);
gamesession.save(function(err, gamesession){
if(err) return next(err);
res.json(gamesession);
res.status(201);
});
});
});
Here is my UserSchema
var UserSchema = new Schema({
posts: [PostSchema],
sessions: [GameSessionSchema],
email: {
type: String,
unique: true,
required: true,
trim: true
},
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true
}
});
And my GameSessionSchema
var GameSessionSchema = new Schema({
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
region: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
game: {
type: String,
required: true
},
age: String,
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now},
platform: {
type: [String],
enum: ["Xbox One", "PS4", "PC"],
required: true
}
});
Edit: Adding my delete route to see if that helps
//DELETE /posts/:id/comments/:id
//Delete a specific comment
router.delete("/gamesessions/:uID/sessions/:gID", function(req, res) {
var gamesession = new GameSession(req.body);
gamesession.remove(function(err) {
req.user.save(function(err, user) {
if(err) return next(err);
res.json(user);
});
});
});
Then, when I want to delete a gamesession with a route, it only deletes the instance saved in user.sessions and when I want to query all gamesessions, it's still there, but deleted in my User document. Any ideas? I think it's because I'm saving the document twice, and if so, what's the best way to save it in user.sessions while also being able to delete from user.sessions and querying a global session.
Possibly not saving the removed gamesession from the GameSession document?
router.delete("/gamesessions/:uID/sessions/:gID", function(req, res) {
var gamesession = new GameSession(req.body);
gamesession.remove(function(err) {
req.user.save(function(err, user) {
if(err) return next(err);
gamesession.save(function(err, gamesession){
if(err) return next(err);
res.json({message: 'Updated GameSession Doc'}, gamesession)
})
res.json(user);
});
});
});

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);
}
});
});

Save array of ObjectId in Schema

I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.

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