Creating new mongoose sub-doc and appending to existing parent doc - javascript

I'm building a website with a database using NodeJS, MongoDB, Express, Mongoose etc.
I have two schema set up: Events and a sub-doc schema Categories (among others).
The function pulls in array which contains the data needed to create several categories (this bit works) as well as the Event ID appended to the end.
The first few bits below just grab that ID, then remove it from the array (probably a better way to do this, but again, it works).
As mentioned above, the Categories then create correctly (and even do validation), which is amazing, BUT...
They don't get appended to the Event doc. The doc updates the "categories" field to an applicable number of "null" values, but I cannot for the life of me get it to actually take the IDs of the newly created categories.
I nabbed (and adjusted) the below code from somewhere, so this is where I'm at...
exports.addCategories = catchAsync(async (req, res, next) => {
const categories = req.body;
const length = categories.length;
const eventID = categories[length - 1].eventId;
categories.pop();
Event.findOne({ _id: eventID }, (err, event) => {
if (err) return res.status(400).send(err);
if (!event)
return res.status(400).send(new Error("Could not find that event"));
Category.create(categories, (err, category) => {
if (err) return res.status(400).send(err);
event.categories.push(category._id);
event.save((err) => {
if (err) return res.status(400).send(err);
res.status(200).json(category);
});
});
});
});
Currently the mongoose debug output is showing the following (which confirms that MOST of it is working, but the IDs just aren't being pulled correctly):
> Mongoose: events.updateOne({ _id: ObjectId("614bc221bc067e62e0790875")}, { '$push': { categories: { '$each': [ undefined ] } }, '$inc': { __v: 1 }}, { session: undefined })

Nevermind! I realised that "category" was still an array, rather than an element of the categories array as I'd assumed.
So I replaced that section with this, and now... it works!
Category.create(categories, (err, categories) => {
if (err) return res.status(400).send(err);
categories.forEach((category) => {
event.categories.push(category._id);
});
event.save((err) => {
if (err) return res.status(400).send(err);
});
});

Related

Is there a way to find ids of two separate collections in one js function?

I have two collections in one database – "Neighborhood" and Restaurants".
I am trying to find the id of the neighborhood that I am currently in so that my "back" button on the restaurant.ejs page takes me back to the page with the path that contains that neighborhood's id.
I've tried:
findById({} ...
findById(id, ....
but I keep getting errors.
Here is my code – thinking I just need to add something in the ??? to fix:
neighborhood.get('restaurant/:id', (req, res) => {
Restaurant.findById(req.params.id, (err, restaurantInfo) => {
Neighborhood.findById(???, (err, foundNeighborhood) => {
res.render('restaurant.ejs', {
restaurant: restaurantInfo,
neighborhood: foundNeighborhood
})
})
})
});
If I understand your question correctly, either the restaurantInfo being returned will have some information about the neighborhood, as #paolo metioned above, or you would have to provide some way to look up the neighborhood in the req, maybe like this:
Restaurant.findById(req.params.id, (err, restaurantInfo) => {
Neighborhood.findById(req.params.neignborhoodId, (err, foundNeighborhood) => {
res.render('restaurant.ejs', {
restaurant: restaurantInfo,
neighborhood: foundNeighborhood
})
})
})
});
If you're getting errors, it would be good to check for errors in the callback like this:
Restaurant.findById(req.params.id, (err, restaurantInfo) => {
if (err) {
return err // you can add an error message here, too
} else {
Neighborhood.findById(req.params.neignborhoodId, (err, foundNeighborhood) => {
if (err) {
return err
}
res.render('restaurant.ejs', {
restaurant: restaurantInfo,
neighborhood: foundNeighborhood
}
})
})
})
});
I think the easiest, depending on your project, would be to include the neighborhood id as part of the restaurant info, then you could pass in the neighborhood id like this:
Neighborhood.findById(restaurantInfo.neighborhoodId, (err, foundNeighborhood) => {
If possible, you could use the neighborhood name to find the neighborhood id, since you said the restaurantInfo contains the neighborhood name. Or you could use a Neighborhood.findByName search instead of a findById.

How to query all articles from a specific user?

CODE:
server-side
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ 'user.displayName': 'GIGANTOR !' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
SITUATION:
What I tried above does not work. I checked the mongoose docs: http://mongoosejs.com/docs/queries.html
but can't seem to get the query to work. Currently, the query just returns nothing.
QUESTION:
How to query all articles by a user with a specific displayName ?
TL;DR You can't query a document by a field that belongs to a populated object.
Since article simply has a ref to User, you'll have just get all articles, and then filter them in memory. Or, since the article.user field is an _id, you can find articles by the user ID (but your question is asking about finding them by user.displayName).
Mongoose populate does not do the populating in the MongoDB server itself; it populates on the application server. This means that multiple round-trips to the database are happening (see article Understanding Mongoose Population.) Therefore, you can't query by a field that exists as part of a populated object.
So, here's your 2 solutions:
Article.find({}).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
let filteredArticles = articles
.filter(article => article.user.displayName === 'GIGANTOR !');
res.json(filteredArticles);
}
});
Or, if you can query by _id, you can do this:
Article.find({ user: 'somemongoobjectidofuser' }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
It gets to be a bit hairy and out of scope of the question, but another solution is the aggregation pipeline, which is only usually recommended for backend analytics. But, it'll provide you more flexibility in your query (especially if you user MongoDB's new $graphLookup).
Or, you can always store a copy of the user as a denormalized object inside the article document itself, but then you run into the much-discussed issue of maintaining denormalized documents in-sync.
Just putting the code I ended up using here for people who could need it:
/**
* List of Articles
*/
exports.list = function (req, res) {
Article.find({ user: req.user._id.toString() }).sort('-created').populate('user', 'displayName').exec(function (err, articles) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};

Dynamically added object attributes not available

I have an array of Stock objects and try to attach n Report objects to each of the Stock objects:
router.get('/stocks', function (req, res, next) {
Stock.find({}, function (err, stocks) {
if (err) {
next(err)
return
}
async.map(stocks, function (stock, callback) {
Report.find({ 'isin': stock.isin }).sort('-created').limit(10).exec(function (err, reports) {
if (err) {
next(err)
return
}
stock.reports = reports
return callback(null, stock)
})
}, function (err, stocks) {
if (err) {
next(err)
return
}
res.json(stocks)
})
})
})
What I get is the list of stock objects without the reports... What I want is instead the same stocks, but with the additional attribute reports set.
Most interesting is the fact, that console.log(stock) before and after the assignment stock.reports = reports is the same, but console.log(stock.reports) delivers the actual array of report objects...
I found the solution in this other Stackoverflow topic. The solution was the following:
And because mongoose ignores fields that does not exist in the schema...
Because the reports object was not in my stock model, mongoose ignored it... The solution was to add it to mongoose:
const StockSchema = new mongoose.Schema({
...
reports: {
type: mongoose.Schema.Types.Mixed
},
...
})
Blind shot: Sometimes the "dot. notation" fails if attribute doesn't exist. You could try:
stock['reports'] = reports
instead of
stock.reports = reports

nodejs filter for mongodb data

I am using node and MongoDB for my project, here I need to set the filter on node script so that the data comes out from my MongoDB is filtered. But I do not understood how to use filter functionality on my script. It will be a filtered method, or I can use find method, or use loop
to filter the data in my MongoDB using node js script.
I am not exactly getting the idea.
What I want to do?
1) I had sent a set off a question from my MongoDB to the frontend.
2) Questions Data is coming from the front end and saved via using node js API. I have to save questionId, a score.
3) Next time when I have to send the data I NEED FILTER HERE so that data I have sent previously not sent again.
Id will generate automatically in MongoDB. So I have not mention id here.
This is my question schema
title: {type: String, required:true},
options: {type: Array, required:true},
result: {type: Array, required:true},
here i am storing my questionid, score values coming from frontend.
This is my question id, score save schema
child: {
quiz: {
questionId:{type:String},
score:{type:Number},
time:{type:String}
}
}
This is my node js API filter. I am trying this but I am not sure this is right or wrong. Please help me to fix this proble.m
this.childfilter = function(req, res, next) {
async.waterfall ([
function(callback) {
try {
var query = { 'child.quiz.score': 1 };
var projection = '';
childinfo.find(query,function(err,data) {
if(err) return next(err);
res.send(data);
callback(null, data)
});
}
catch(err) {
console.log(err);
return next(err);
}
},
function(callback, data) {
try {
var childq = new childquestion();
var query = { 'data.child.quiz.questionId' : childq._id };
var projection = 'id title options result';
childquestion.find(query,projection)
.skip()
.exec(function(err,data){
if (err) return next(err);
res.send(data);
});
}
catch(err) {
console.log('Error While Saving the result ' +err);
return next(err);
}
}
]);
}

MongooseJS Not saving to array properly

I want to append a value into my Mongoose array but my array never seems to update. I do the following:
In my controller, I append an eventName into the array eventsAttending like so:
$scope.currentUser.eventsAttending.push(event.eventName);
$http.put('/api/users/' + $scope.currentUser._id, $scope.currentUser)
.success(function(data){
console.log("Success. User " + $scope.currentUser.name);
});
I try to update the array like so:
// Updates an existing event in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
User.findById(req.params.id, function (err, user) {
if (err) { return handleError(res, err); }
if(!user) { return res.send(404); }
user.markModified('req.body.eventsAttending');
user.save(function (err) {
if (err) { return handleError(res, err);}
return res.json(200, user);
});
});
};
But my array never seems to update. I've also tried the following:
// Updates an existing event in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
User.findById(req.params.id, function (err, user) {
if (err) { return handleError(res, err); }
if(!user) { return res.send(404); }
var updated = _.merge(user, req.body);
updated.markModified('eventsAttending');
updated.save(function (err) {
if (err) { return handleError(res, err);}
return res.json(200, user);
});
});
};
With this approach, my array updates properly, but when I try to perform the http put after one time, I get an error saying Error: { [VersionError: No matching document found.] message: 'No matching document found.', name: 'VersionError' }
Here is my UserSchema:
var UserSchema = new Schema({
name: String,
username: String,
eventsAttending: [{ type: String, ref: 'Event'}],
});
If anyone could help that would be much appreciated.
My guess is the object returning from _.merge is no longer a Mongoose model and some information is getting lost in the transform. I would try manually setting all of the fields coming from the request and use events.attending.push() to add to the array, then saving the updated object and see what happens.
Your first example with markModified looks wrong. Looking at the documentation it should be the name of the field that is modified and it appears that you've put the source location for it.
user.markModified('user.eventsAttending')
However that should not be necessary if you use the push method as Mongoose overrides the built-in array function to track changes.

Categories

Resources