Save array of ObjectId in Schema - node.js

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.

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 modify multi level subdocument then save not work normally

I have a Torrent item, it has subdocument array named '_replies' to saved user comments, and every comment also include subdocument array '_replies' to saved user reply, this is my all schema define:
var CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
comment: {
type: String,
default: '',
trim: true
},
_replies: [this],
createdat: {
type: Date,
default: Date.now
},
editedby: {
type: String,
default: '',
trim: true
},
editedat: {
type: Date,
default: ''
}
});
var TorrentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
torrent_filename: {
type: String,
default: '',
trim: true,
required: 'filename cannot be blank'
},
torrent_title: {
type: String,
default: '',
trim: true,
required: 'title cannot be blank'
},
_replies: [CommentSchema]
});
mongoose.model('Torrent', TorrentSchema);
mongoose.model('Comment', CommentSchema);
the first level comment of torrent update/delete fine, the code of server controller likes below:
exports.update = function (req, res) {
var torrent = req.torrent;
torrent._replies.forEach(function (r) {
if (r._id.equals(req.params.commentId)) {
r.comment = req.body.comment;
r.editedat = Date.now();
r.editedby = req.user.displayName;
torrent.save(function (err) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(torrent); //return data is Correct, and save to mongo is Correct
}
});
}
});
};
but when i used Alike function to update/delete _replies._replies, it can return Correct json of torrent to response, Unfortunate, the save to mongo not fine, the code:
exports.SubUpdate = function (req, res) {
var torrent = req.torrent;
torrent._replies.forEach(function (r) {
if (r._id.equals(req.params.commentId)) {
r._replies.forEach(function (s) {
if (s._id.equals(req.params.subCommentId)) {
s.comment = req.body.comment;
s.editedat = Date.now();
s.editedby = req.user.displayName;
torrent.save(function (err) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(torrent);//return data is Correct, but save to mongo is incorrect
}
});
}
});
}
});
};
also, i can delete first level comment, but can not delete second level comment reply, all the json data of torrent is correct, only not save to mongo.
what can i do more?
I already solve it, i add this code before .save().
torrent.markModified('_replies');
it work fine!

Mongoose: utils.populate: invalid path. Expected string. Got typeof 'undefined'

I am not a totally new populate user but now I do not know what's wrong.
Here I need to populate my designerId which is type of ObjectId. Take a look at my route.
ordersAdminRouter.route('/customorder/add')
.post(function(req, res){
body = req.body;
console.log(body);
CustomOrders.create(body, function(err, saved){
if (err) throw err;
Designs.findByIdAndUpdate(saved.designId, {$set: {status: 'Order Sent'}}, {new: true}).exec()
.then(function(updated){
return CustomOrders.findById(saved._id).populate(saved.designId).exec();
})
.then(function(orders){
res.json(orders);
})
.then(undefined, function(err){
console.log(err);
})
});
});
saved._id is working because when I remove the populate, it returns the document that I need without the populated document of course.
Take a look at my schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var customOrderSchema = new Schema({
designId: { type: Schema.Types.ObjectId, ref: 'customDesigns' },
size: { type: String },
quantity: { type: Number },
totalPrice: { type: Number },
paymentMode: { type: String },
rcpt_img: { type: String },
refNumber: { type: String }
});
module.exports = mongoose.model('customOrders', customOrderSchema);
Here is my customDesigns schema.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var customDesignSchema = new Schema({
item_name: { type: String },
price: { type: Number, default: 0 },
img_url_front: { type: String },
img_url_back: { type: String },
designer: { type: Schema.Types.ObjectId, ref: 'users' },
color: { type: String },
designDate: { type: Date, default: Date.now() },
status: { type: String, default: 'For Qoutation' }
});
module.exports = mongoose.model('customDesigns', customDesignSchema);
I need to admit that I am new to promises on mongoose & express and this is my first time doing so. But using populate, i use it more than I can think of. Any suggestions?
return CustomOrders.findById(saved._id).populate('designId').then(.. your code);
By the way, you dont must use .exec() then you want execute your query, .then executes query as well. You can skip .exec()
http://mongoosejs.com/docs/populate.html
http://mongoosejs.com/docs/api.html#query_Query-populate

product is not added to my collection

In my project I have 2 models: Store and product, basically every store can have a number of products, but each product can be related to 1 store, so to build the models I did this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var lojasSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true
},
nome: {
type: String,
required: true,
unique: true
},
descricao: {
type: String,
default: "No description for this store"
},
telefone:{
type:String,
},
password:
{
type: String, required: true
}
,
img: {
data: Buffer, contentType: String
},
imgNome: {
type: String
},
produtos: [
{ type: mongoose.Schema.ObjectId, ref: 'Produto' }
],
});
module.exports = mongoose.model('Loja', lojasSchema);
my product is like this
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var produtoSchema = mongoose.Schema({
nome:{
type:String, required:true
},
stock:{
type:Number
},
descricao:{
type:String
},
categoria:{
type: String, required:true
},
tamanho:{
type: String, required:true
},
data:{
type: Date, default: Date.now
},
preco:{
type: Number,required:true, default: 0
}
});
module.exports = mongoose.model('Produto',produtoSchema);
as you guys can see I have the reference on the store side, basically what I want is every time I add a product I add that product directly to a store(storeID), so to do that I added this to my store side routes:
//adiciona um produto a uma loja especifica
router.post('/:id/produtos',function(req,res){
Loja.findById(req.params.id,function(err,loja){
if(!loja){
return res.status(404).json({Error:"Loja nao encontrada"});
}
if(JSON.stringify(req.body) == "{}")
{
return res.status(400).json({Error:"Your request is empty"});
}
var produto = new Produto(req.body);
loja.produtos.push(produto);
loja.save(function(err){
if(err){
return res.status(500).json({Error:"Server problem"});
}
res.status(200).json({message: "product added"});
});
})
})
I got 2 problems: when I go to my get products route, I get an empty array, I should get there all the products, I think the product is not getting added in the product's model, the second problem is: every time I add a product to my store, and go to see all my stores with the .populate I just can see an id inside the products array, I should see all the product details, what am I doing wrong?:S
Update you mongoose to 4.8.1 because 4.7.6 is buggy with casting Ids.
then go to your lojas.js and require mongoose at the top.
Replace your code with this
router.post('/:id/produtos',function(req,res){
var queryObject = {_id : mongoose.Types.ObjectId(req.params.id)};
Loja.findById(queryObject,function(err,loja){
console.log(err);
if(!loja){
return res.status(404).json({Error:"Loja nao encontrada"});
}
if(JSON.stringify(req.body) == "{}")
{
return res.status(400).json({Error:"Your request is empty"});
}
var produto = new Produto(req.body);
produto.save(function (err) {
if(err){
return res.status(500).json({Error:"Server Problem"})
}
loja.produtos.push(produto._id);
loja.save(function(err){
if(err){
return res.status(500).json({Error:"Server problem"});
}
res.status(200).json({message: "product added"});
});
});
})
})

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