Mongoose nested document update failing? - node.js

If I have a nested document, how can I update a field in that nested document in Mongoose?
I carefully researched this problem using everything available I could find, and even changed my test code to match a similar answered question about this here on Stackoverflow, but I am still unable to figure this out. Here are is my Schema and Models, the code, and the Mongoose debug output. I am unable to understand what I am doing wrong, here.
var mongoose = require('mongoose')
, db = mongoose.createConnection('localhost', 'test')
, assert = require("node-assert-extras");
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
db.once('open', function () {
// yay!
});
mongoose.set('debug', true);
var PDFSchema = new Schema({
title : { type: String, required: true, trim: true }
})
var docsSchema = new Schema({
PDFs : [PDFSchema]
});
var A = db.model('pdf', PDFSchema);
var B = db.model('docs', docsSchema);
function reset(cb) {
B.find().remove();
// create some data with a nested document A
var newA = new A( { title : "my title" })
var newB = new B( { PDFs: newA});
newB.save();
cb();
}
function test1( ) {
reset(function() {
B.findOne({}, 'PDFs', function(e,o)
{
console.log(o);
pdf_id = o.PDFs[0]._id;
console.log("ID " + pdf_id);
B.update(
{ 'pdfs.pdf_id': pdf_id },
{ $set: {
'pdfs.$.title': 'new title'
}}, function (err, numAffected) {
if(err) throw err;
assert.equal(numAffected,1); //KA Boom!
}
);
});
});
}
test1();
/*
$ node test2.js
Mongoose: docs.remove({}) {}
Mongoose: docs.findOne({}) { fields: { PDFs: 1 }, safe: true }
Mongoose: docs.insert({ __v: 0, PDFs: [ { _id: ObjectId("50930e3d0a39ad162b000002"), title: 'my title' } ], _id: ObjectId("50930e3d0a39ad162b000003") }) { safe: true }
{ _id: 50930e3d0a39ad162b000003,
PDFs: [ { _id: 50930e3d0a39ad162b000002, title: 'my title' } ] }
ID 50930e3d0a39ad162b000002
assert.js:102
throw new assert.AssertionError({
^
AssertionError: 0 == 1
*/

You're not using the correct field names in your B.update call. It should be this instead:
B.update(
{ 'PDFs._id': pdf_id }, // <== here
{ $set: {
'PDFs.$.title': 'new title' // <== and here
}}, function (err, numAffected) {
if(err) throw err;
assert.equal(numAffected,1);
}
);
You should also fix your reset function to not call its callback until the save completes:
function reset(cb) {
B.find().remove();
// create some data with a nested document A
var newA = new A( { title : "my title" })
var newB = new B( { PDFs: newA});
newB.save(cb); // <== call cb when the document is saved
}

Related

Mongoose is only returning ID from MongoDB

I am currently trying to incorporate datatables with my MongoDB database. I am having some trouble accessing the returned object though. The main problem I am seeing is that I am only getting the _id returned from MongoDB, and no values of the object.
Heres the code I am using to pass the information to the datatables.
var itemsModel = require('./models/itemReturn');
exports.getItemList = function(req, res) {
var searchStr = req.body.search.value;
if (req.body.search.value) {
var regex = new RegExp(req.body.search.value, "i")
searchStr = { $or: [{ 'productName': regex }, { 'itemPrice': regex }, { 'Quantity': regex }, { 'Description': regex }, { 'seller': regex }] };
} else {
searchStr = {};
}
var recordsTotal = 0;
var recordsFiltered = 0;
itemsModel.count({}, function(err, c) {
recordsTotal = c;
console.log(c);
itemsModel.count(searchStr, function(err, c) {
recordsFiltered = c;
itemsModel.find(searchStr, 'productName itemPrice Quantity Description seller', { 'skip': Number(req.body.start), 'limit': Number(req.body.length) }, function(err, results) {
if (err) {
console.log('error while getting results' + err);
return;
}
var data = JSON.stringify({
"draw": req.body.draw,
"recordsFiltered": recordsFiltered,
"recordsTotal": recordsTotal,
"data": results
});
console.log(data);
res.send(data);
});
});
});
};
This is the model
// app/models/itemsReturn.js
// load the things we need
var mongoose = require('mongoose');
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
},
toObject: {
virtuals: true
}
};
// define the schema for our item model
var itemsReturned = mongoose.Schema({
productName: String,
itemPrice: String,
Quantity: String,
Description: String,
seller: String
}, schemaOptions);
// create the model for users and expose it to our app
var items = mongoose.model('items', itemsReturned);
module.exports = items;
The thing is that I know its not a data table issue as I can make the _id appear in the tables. I just need to know how to return the entire object instead of just the _ID so that I can access the values of the object.
If it helps this is the tutorial I am following.
UPDATE: Okay so I figured out why my MongoDB collections were only returning the item ID. The issue was that I had stored everything in the local database (oops).

populate query does not show newly added referenced item

My schema is as follows:
items.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ItemSchema = new Schema({
no_of_times_ordered:Number,
item_name:String,
item_tag:String,
item_category:String,
item_illustrations:[String],
item_stock:Number, //0 available 1 last 5 items 2 not available
item_quantity_ordered:{type:Number,default:0},
item_discount_price:Number,
item_price:Number,
item_img:String,
no_of_likes:{type:Number,default:0}
},{ versionKey: false });
module.exports = mongoose.model('items',ItemSchema);
foodtruck.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Items = require('./items.js');
var FoodTruckSchema = new Schema({
foodtruck_name:String,
foodtruck_location:String,
foodtruck_rating:{type:Number,default:5},
foodtruck_total_votes:{type:Number,default:0},
foodtruck_tag:String,
foodtruck_open_status:{type:Number,default:1}, //0 open 1 closed
foodtruck_starting_timing:String,
foodtruck_closing_timing:String,
foodtruck_cusine:String,
foodtruck_img:String,
foodtruck_logo:String,
item_list: [ {type : mongoose.Schema.ObjectId, ref : 'items'}]
},{ versionKey: false });
module.exports = mongoose.model('foodtruck',FoodTruckSchema);
My query is as below:
var addItem = function(req, res) {
var foodtruck_id = req.body.foodtruck_id;
var newItem = new item();
var itemList = [];
newItem.item_name = req.body.item_name;
newItem.item_tag = req.body.item_tag;
newItem.item_category = req.body.item_category;
for (var key in req.body) {
if (req.body.hasOwnProperty(key)) {
if (key == 'item_illustrations') {
newItem.item_illustrations = req.body[key];
}
}
}
newItem.item_stock = req.body.item_status;
newItem.item_price = req.body.item_price;
if ((foodtruck_id) && (foodtruck_id.trim() != '')) {
foodtruck.findById(foodtruck_id.trim(), function(err, foodtrucks) {
if (err)
res.json({
status: '500',
message: 'There is no data available'
});
newItem.save(function(err, savedItem) {
if (!err) {
foodtrucks.item_list.push(savedItem._id);
foodtrucks.save();
foodtruck.find({
_id: foodtruck_id.trim()
}).populate('item_list').exec(function(err, foodtrucks) {
res.json({
status: '200',
message: 'New item added successfully',
data: foodtrucks
});
});
} else {
res.json({
status: '500',
message: 'Error while saving new item'
});
}
});
});
}
}
The main problem I am facing is that, I am able to create new item ,add its reference to the foodtruck schema, but somehow when I put populate query for the same foodtruck, the newly created item does not show. So,can you tell me how exactly I can show this item via populate query?
I think you got to put the populate inside the save function to make the populate method happen after the save.
foodtrucks.save(function(err, doc) {
//do population here
});
using asynchronous methods.

How to convert multiple Mongoose documents?

Each of my schemas have a method, called toItem() which converts the doc to a more verbose / human-readable form. How can I create a toItems() method to do the same thing for an array of documents?
My example schema:
var mongoose = require('mongoose');
var membershipSchema = new mongoose.Schema({
m : { type: mongoose.Schema.ObjectId, ref: 'member' },
b : { type: Date, required: true },
e : { type: Date },
a : { type: Boolean, required: true }
});
var accountSchema = new mongoose.Schema({
n : { type: String, trim: true },
m : [ membershipSchema ]
});
accountSchema.methods.toItem = function (callback) {
var item = {
id : this._id.toString(),
name : this.n,
members : []
};
(this.m || []).forEach(function(obj){
item.members.push({
id : obj.m.toString(),
dateBegin : obj.b,
dateEnd : obj.e,
isAdmin : obj.a
});
});
return callback(null, item);
};
var accountModel = mongoose.model('account', accountSchema);
module.exports = accountModel;
I've tried using statics, methods, and third-party libraries, but nothing clean works. I would like to keep this as simple / clean as possible and have the toItems() function contained within my model file.
Thank you, in advance.
Your toItem() method is specific to the schema / model. Your toItems() method sounds more like a utility method which can / will be used by all of your models. If so, I would move create the toItems() method inside a utility file. You would simply pass in the array of documents and the utility method would call the individual toItem() method on each document.
For example:
var async = require('async');
var toItems = function (models, callback) {
models = models || [];
if (models.length < 1) { return callback(); }
var count = -1,
items = [],
errors = [];
async.forEach(models, function (model, next) {
count++;
model.toItem(function (err, item) {
if (err) {
errors.push(new Error('Error on item #' + count + ': ' + err.message));
}
else {
items.push(item);
}
next();
});
}, function (err) {
if (err) {
return callback(err);
}
if (errors.length > 0) {
return callback(errors[0]);
}
return callback(null, items);
});
};
module.exports.toItems = toItems;

How come Mongoose return empty document when find by id of an array?

The schema is like that:
var feedSchema = new mongoose.Schema({
id: {type: Number},
following: []
});
and my code using promise is very simple:
var Feed = require("/models/feed");
return Feed.find({"following" : id}).exec();
I have data like this:
{
id:1, following: [2,3,4]
}
but when I set the id for query, it doesn't return anything. Any idea?
It should be working, I am providing an example which works ok, probably you can contrast your logic with this one and know which part could be the problem.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost:27017/murvinlai');
var FeedSchema = new Schema({
id: Number,
following: [Number]
});
var Feed = mongoose.model('Feed', FeedSchema);
var newFeed = {
id: 1,
following: [2, 3, 4]
};
createFeed(newFeed, function(err, feed) {
if (err) throw err;
findFeedByFollowingArray(2).then(function(feeds) {
console.log(feeds);
// [ { _id: 55a486a11ef682b41e13e82a,
// id: 1,
// __v: 0,
// following: [ 2, 3, 4 ] } ]
});
});
function createFeed(feed, cb) {
Feed.create(feed, function(err, feed) {
if (err) { return cb(err); }
if (!feed) { return cb(new Error('Feed was not created')); }
cb(null, feed);
});
}
function findFeedByFollowingArray(id) {
return Feed
.find({following: id})
.exec();
}

Mongoose cascading deletes in same model

This is different than this and this. But they are very helpful.
Basically, I have a Topic schema. If one Topic get's deleted, I want to delete other topics. Think of a graph where deleting a node means deleting the edges.
var schema = new Schema({
title: { type: String, required: true, trim: true },
srcId: { type: Schema.Types.ObjectId, validate: [edgeValidator, 'Set both srcId and destId, or neither'] },
destId: Schema.Types.ObjectId,
});
I want the 2nd mongo delete to run in the schema.pre('remove', ...)
But I don't have a model at this point. So calling .find() or .remove() doesn't work. What's the best way?
schema.pre('remove', function(next) {
var query = '';
var self = this;
if (this.isEdge) {
query = 'MATCH ()-[r:CONNECTION { mongoId: {_id} }]-() DELETE r;';
} else {
query = 'MATCH (n:Topic { mongoId: {_id} })-[r]-() DELETE n,r;';
}
// This is another database.
neo.query(query, this, function(err, data) {
if (err) return next(err);
if (self.isEdge) {
return next();
} else {
// Now we're back to mongoose and mongodb
// Find and remove edges from mongo
schema.find({ mongoId: { // <------ .find() is undefined
$or: [
{ srcId: self._id },
{ destId: self._id }
]
}}, function(err, edges) {
edges.remove(next);
});
}
});
});
This turned out to be pretty easy.
var Model = null;
var schema = ...
module.exports = Model = mongoose.model('Topic', schema);
Then just use Model in the pre-remove. Piece of pie.

Resources