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);
}
});
};
Related
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);
});
});
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
the code is this
module.exports = {
index: function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
var elusuario=[];
User.findOne(2, function foundUser (err, user) {
if (err) return next(err);
if (!user) return next();
console.log(user);
console.log("----------------------------");
elusuario = user;
console.log(elusuario);
});
res.view({
notifications: notifications,
elusuario: elusuario
});
})
}
};
That is the controller and in the console prints elusuario good but in the view the user hasn't values. why?
i think is something is something related to the globals variables. but i dont know
thanks
EDIT
all right so the method is async. what im trying to do is find the notifications and the user by her user.id and get the user.name so what if i do this
module.exports = {
index: function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
User.find(function foundUser (err, users) {
if (err) return next(err);
var usuarios_locotes = [];
_.each(notifications, function (notification) {
_.each(users, function (user) {
if(notification.token_user==user.token){
console.log(user.token);
usuarios_locotes.push(user);
console.log(usuarios_locotes);
};
});
});
res.view({
notifications: notifications,
users: usuarios_locotes
});
});
})
}
};
it still not working? the __.each is an async funtion to?
sorry for all this maybe stupid questions
The method findOne of User object runs asynchronously. Because of this, you are rendering the view before the findOne returns the user object.
If you put a console.log before the render.view, it'll print the output before the console.log inner findOne method.
When the code is running, the function foundNotification is not executed before you call the res.view. My advice for you is read about Promises.
You can change your code as below to work:
function (req, res, next) {
//get an array of all users in user collection
Notification.find(function foundNotification(err, notifications) {
if (err) return next(err);
var elusuario=[];
User.findOne(2, function foundUser (err, user) {
if (err) return next(err);
if (!user) return next();
console.log(user);
console.log("----------------------------");
elusuario = user;
console.log(elusuario);
res.view({
notifications: notifications,
elusuario: elusuario
});
});
});
}
the findOne Method is an asynchrone method,so it's executed without provinding the res.view with the appropriate data
try to wrap the whole logic in the same function, it may look ugly but it ll do the thing for now
All right so.. first really thanks to everybody. I solve this shit.
I know this is not the right way to do this but it works, so for my proposes it's fine.
the problem after the EDIT was that in the view I'm trying to write an object with parameters but what I've was sending was vector of vector so changing this line:
usuarios_locotes.push(new Object(users[h]));
I can send a vector of objects.
So.. anyway thanks cause later i will change my code to do it better and efficient
This was my first post so sorry for not read the first steps of how to use this haha cause i think i have been make a lot of mistakes.
And sorry for my English :C
I currently have the following code:
User.find({ featuredMerchant: true })
.lean()
.limit(2)
.exec(function(err, users) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
Listing.find({
user: user
}).populate('listings', 'displayName merchantName userImageName hasUploadedImage').exec(function(err, listings){
user.listings = listings;
});
});
});
res.jsonp(users);
}
});
As you can see I am trying to add the retrieved listings to each 'user' in the 'users' lean object that I have returned. If I do a console.log(user) inside the Listing.find exec method after adding 'user.listings = listings', the result is as I would expect; a user object with a listings property, with this listing property containing all the listings retrieved.
However, if I console.log the 'users' object, the listings for each user cannot be found.
I'm pretty sure I'm doing something stupid here, but I really cannot work out what. Any help would be greatly appreciated. Thank you!
you right about stupid thing !
No offense, I think this is a common mistake :)
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
Listing.find({
user: user
})
.populate('listings', 'displayName merchantName userImageName hasUploadedImage')
.exec(function(err, listings){
user.listings = listings;
});
});
});
// Listing.find inside foreach hasn't finish yet
// I suppose it's always an asynchronous call
res.jsonp(users);
Maybe you can fix it using promises. This an example with q library.
var promises = [];
_.forEach(users, function(user){
_.forEach(user.userListings, function(listing){
var deferred = q.defer();
promises.push(deferred);
Listing.find({
user: user
})
.populate('listings', 'displayName merchantName userImageName hasUploadedImage')
.exec(function(err, listings){
user.listings = listings;
deferred.resolve(user);
});
});
});
q
.all(promises)
.done(function(allUsers){
// Do what you want here with your users
res.jsonp(allUsers);
});
Check this and don't hesitate to fix it because I can't test it.
Thank you both for your input. It's truly appreciated. I managed to solve this an easier way which now I come to think of it is pretty obvious - but hey we live and learn. Basically, my 'userListings' model field was an array of Object Id's and I wanted to add the physical listings from the listings model into the data returned. The following code did the trick for me.
exports.findFeaturedMerchants = function(req, res, next) {
User.find({ featuredMerchant: true })
.populate('userListings')
.limit(2)
.exec(function(err, data) {
_.forEach(data, function(user){
Listing.populate(user.userListings, '', function(err, user){
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
});
});
console.log(data);
res.jsonp(data);
});
};
I simply had to populate each userListings object into the user, by using the populate function twice - once for the user and another time for each listing. I was finding it tricky to get my head around - probably because I didn't understand how the populate function worked exactly, but there we go :)
Jamie
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.