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
Related
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);
}
});
};
I am implementing a post upvote system that limits by IP. So far, the route for upvoting a single post contains 4 total queries in order to complete these steps:
Look for an already existing upvote with same PostId and IP and fail if one exists
-otherwise-
Create an upvote
Find the post to associate the upvote with and associate them.
Lastly re-fetch the post to include the upvote that was just associated.
I feel like the last two steps could be combined, however if I just return the post after associating the upvote to it, it is not included which makes sense because when it was found it had no upvote associated. Here is what I currently have and I feel it's very inefficient for a single upvote.
router.get('/posts/:id/upvote', function(req, res) {
var id = req.params.id;
var query_options = {
where: {
id: id
},
include: common_includes
};
// Look for already existing upvote with same PostId and IP.
Upvote.findOne({ where: { ip: req.ip, PostId: id }}).then(function(upvote) {
if (upvote !== null) return res.fail('Already upvoted');
// No upvote exists, create one
Upvote.create({
ip: req.ip
}).then(function(upvote) {
// Find post to associate upvote with
Post.findOne({ where: { id: id }}).then(function(post) {
// Associate upvote to post
upvote.setPost(post).then(function() {
// Query again to get updated post to be returned
Post.findOne(query_options).then(function(post) {
return res.pass(formatPost(post));
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
}).error(function(err) {
console.log(err);
return res.fail('Server error');
});
});
});
May be helpful http://docs.sequelizejs.com/en/latest/docs/associations/#creating-with-associations .
But IMHO you can combine step 2 and 3 with:
Upvote.create({
ip: req.ip,
PostId: id
})
and then fetch the new post
apiRoutes.put('/intake/:id', function(req, res) {
var id = req.params.id;
Intake.findById({id, function(err, intake) {
if (err)res.send(err);
intake.check = true;
intake.save(function(err) {
if (err) {return res.json({success: false, msg: 'Error'});}
res.json({success: true, msg: 'Successful update check state.'});
});
}})
});
What's problem? In console i see ID, it's ok, but database have no change
Intake.findById(/*remove { here*/id, function(err, intake) {
if (err)res.send(err);
intake.check = true;
intake.save(function(err) {
if (err) {return res.json({success: false, msg: 'Error'});}
res.json({success: true, msg: 'Successful update check state.'});
});
})
You gave us too few information to help you. But I got some hints on how to find out what's going wrong: (I added example code a the end of my answer.)
Use a proper formatting of your code. Mistakes are easier to find.
Please avoid res.send(err). Most express apps provide an error to HTML Page translation at the end of the route pipe. This only works if you call res.next(err);. If your app does not provide error page generating at the end of the pipe then, you could use res.status(400).json({success: false, msg: err.message});.
After the use of res.send or res.next or res.json you have to end the execution of the function by return before one of these functions can be called a second time. This can be very complicated in case of nested or asynchrounous method calls. But in your example it's quite easy.
Use some log outputs to see which part of the code you reach and which not.
console.dir(<object>); prints out the structure of this object.
Hope this helps a little bit. ;-)
apiRoutes.put('/intake/:id', function(req, res)
{
var id = req.params.id;
Intake.findById(id, function(err, intake)
{
if (err)
{
res.next(err);
console.error(err);
return;
}
console.log("Modify check attribute");
intake.check = true;
console.dir(intake);
intake.save(function(err)
{
console.log("Intake save called!");
if (err)
{
console.error(err);
res.json({
success: false,
msg: 'Error'
});
return;
}
console.log("Success");
res.json({
success: true,
msg: 'Successful update check state.'
});
});
}})
});
you can use this
Intake.update({_id: req.params.id},
{
check : true
},
function(err){
if(err){
console.log(err);
res.status(400).send(err.errors);
}else{
res.status(200).end();
}
});
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 understand that there is no built-in way in Sails.js/Waterline of populating deep nested associations yet, so I am trying to use bluebird promises to accomplish that but I'm running into a problem.
I'm successfully retrieving the user, and all the posts (populated with the images collection) associated with it (console.log shows me that everything is filled properly). However, when I override the property "post" of the user and try to assign the fully populated posts retrieved before, it does not fill properly the images property of Post.js. It is like the ORM is preventing the image collection of Post.js to be manually assigned.
What am I doing wrong? What is the best way of populating deep nested one-to-many associations?
Bellow I've pasted all the code that I'm executing....
// Populate nested association
nested: function (req, res, next){
var username = req.param("id");
User
.findOneByUsername(username)
.populateAll()
.then(function (user){
var posts = Post.find({
"user": user.id
})
.populate('images')
.populate('category')
.then(function (posts){
return posts;
});
return [user, posts];
})
.spread(function (user, posts){
user.posts = posts; // This won't work.... It assigns all the fields properly but the images collection attribute
res.json(user);
}).catch(function (err){
if (err) return res.serverError(err);
});
}
// --- User.js Model --- //
module.exports = {
attributes: {
.....,
posts: {
collection: "post",
via: "user"
},
.....
}
}
// --- Post.js Model --- //
module.exports = {
attributes: {
....,
user: {
model: "user"
},
images: {
collection: "postImage",
via: "post"
},
....
}
}
// --- PostImage.js Model --- //
module.exports = {
attributes: {
....,
post: {
model: "post"
}
},
}
Regards,
Sávio Lucena
This might be an old question, but its better to have an answer, so sails.js users can benefit of it.
Your issue here is that when sails returns a record (Inside an array), the keys of that record that correspond to associations, are in fact getters/setters, and it seems that the setter does not allows what you want. You can use Object.getOwnPropertyDescriptor(user, 'posts') to confirm.
So what you need to do in order to be able to override that property as you want, is to call .toObject on it, (or clone its properties via _.clone or manually looping but you'll get a lot of junk with it, so stick to the .toObject), in any case you get a new object with the properties you need, and there is no restriction in how you modify it now.
So your code will look like this:
User
.findOneByUsername(username)
.populateAll()
.then(function (user){
var posts = Post.find({
"user": user.id
})
.populate('images')
.populate('category')
.then(function (posts){
return posts;
});
return [user, posts];
})
.spread(function (user, posts){
user = user.toObject() // <- HERE IS THE CHANGE!
user.posts = posts; // It will work now
res.json(user);
}).catch(function (err){
if (err) return res.serverError(err);
});
}
You have to overwrite each post id object in user.posts array. For more info check this answer https://stackoverflow.com/a/26452990/4261327.