Dealing with $in using mongoose - node.js

This is the code:
usermodel.findOne({ user: req.session.user }, function (err, user){
usermodel.find({ _id: {$in: user.follow } }, { user: true }, function (err, users){
usermodel.find({ author: { $in: users.user }}, function (err, images){
console.log(users);
console.log(images);
});
});
And this is the Schema:
var userschema = new mongoose.Schema({
user: String,
follow: [String],
message: [{
title: String,
author: String,
message: String
}],
});
The followarray contains the _ids of the users that the actual user is following. With the first usermodel.find y get the follow array. With the second usermodel.find, I get the users names:
[ { _id: 50fd9c7b8e6a9d087d000006, user: 'Mrmangado' },
{ _id: 50fd9d3ce20da1dd7d000006, user: 'kirbo' } ]
And, with the third and last usermodel.find, I'm trying to get the message of the users. I get the users' names in the previous usermodel.find, and the author of the message has the same value of the user that has created it. The problem is the way I get the users' names, I think I have to have the users' names like this way:
[{ user: 'Mrmangado' },
{ user: 'kirbo' }]
If I receive an array like this, the $in statement will work perfectly. Is there any way to get an output like this...?
Thank's advance!

Are you sure that's what you want? The author field is a user's name, not its _id? If so, you could just map the resulting object to get the name.
var usernames = users.map(function(u) { return u.user; });
//= ["Mrmangado", "kirbo"]

Related

add a item inside a nested schema mongoose with addToSet

I know populating schemas is not a new question but I am having a little trouble following the logic on this in regards to multiple schemas. I am working with
"mongoose": "^4.8.5",
"express": "^4.15.0",
I have a schema with a collection of caffeine drinks. When a user selects a drink i would like for that drink to be assigned to the user.
** If at any point I am missing something simple in the architecture please let me know. This project has been my intro to mongodb.
I am reading through populating on the mongoose documentation http://mongoosejs.com/docs/populate.html.
Essentially, if I am to assign the drinks to the list it looks like I want to add them as a reference in an array. This was my approach with caffeine_list
const SelectedDrinks = require('./userDrinks');
const UserSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true
},
caffeine_list: caffeine_list: [ // attempting to reference selected drinks
{
type: mongoose.Schema.Types.ObjectId,
ref: 'SelectedDrinks'
}
]
})
SelectedDrinks comes from the schema below. I added a reference to the user as the creator below
const User = require('./user');
let userDrinkSchema = new mongoose.Schema({
creator : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
caffeine: Number,
mgFloz: Number,
name: String,
size: Number,
updated_at: {
type: Date,
default: Date.now()
}
});
This is where I start to get confused. I initially tried populate but could not get it going. If that was correct please let me know.
In regards to my task of adding a selected drink to the user I used addToSet. I was hoping that this would give me the drink info. I did my set up like so....
const User = require('../../models/user');
const UserDrinks = require('../../models/userDrinks');
router.post('/addDrink', (req, res, next) => {
let newDrink = new UserDrinks({
creator: req.body.creator,
caffeine: req.body.caffeine,
mgFloz: req.body.mgFloz,
name: req.body.name,
size: req.body.size,
updated_at: req.body.updated_at
});
newDrink.save( (err) => {
if(err) {
res.send(err);
} else {
User.findOne({ _id: newDrink.creator}, (err, user) => {
user.caffeine_list.addToSet(newDrink)
user.save( function (err) {
if(err) {
console.log(err);
}else {
res.status(201).json(newDrink);
}
})
})
}
})
});
However, after i do a post in postman I check caffeine_list and the result is
"caffeine_list" : [
ObjectId("58d82a5ff2f85e3f21822ab5"),
ObjectId("58d82c15bfdaf03f853f3864")
],
Ideally I would like to have an array of objects being passed with the caffeine info like so
"caffeine_list" : [
{
"creator": "58d6245cc02b0a0e6db8d257",
"caffeine": 412,
"mgFloz": 218.7,
"name": "1.95 Perfect Drink!",
"size": 42.93,
"updated_at": "2017-03-24T18:04:06.357Z"
}
]
Change your else part with below code instead of findOne and save use update
User.update(
{ _id: newDrink.creator},
{ $addToSet:{
caffeine_list: newDrink
}}).exec(function (err, updatedrink){
if(err) {
console.log(err);
}else {
res.status(201).json(updatedrink);
}
})
Although I am not sure this is the best approach I did find this to be give me the result that I was desiring. I had to make two small changes and I was able to get the caffeine_list to give me the desired response
I had to access the schema for selected drinks
const SelectedDrinks = require('./userDrinks').schema; //** need schema
Afterwards I was able to change
caffeine_list: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'UserDrinks' // name of the file
}
]
to
caffeine_list: [SelectedDrinks]
Now that I have the schema I am able to add the drinks directly into the caffeine_list on the UserSchema.

Big query in MongoDB with Mongoose

I have an User model defined. This model has two lists, one of the items the user has liked and the other one is of the items the user has disliked.
I need a list of the items that one User hasn't qualified (neither liked nor disliked) and the other users did. I'm using the Mongoose library for NodeJS and also the lodash(_) library,
my code looks like this:
function itemsUserHasntQualified(var user){
items = [];
User.find().exec(function(err, users){
for(var user_it: users){
if(user_it != user){
items.push(_.difference(user_it.tracks.liked, user_it.tracks.disliked, user.tracks.liked, user.tracks.disliked);
}
}
});
}
This is the schema for the User:
var UserSchema = new Schema({
name: String,
username: {type: String, lowercase:true },
email: { type: String, lowercase: true },
role: {
type: String,
default: 'user'
},
hashedPassword: String,
provider: String,
salt: String,
facebook: {},
twitter: {},
google: {},
github: {},
tracks: {
liked: [{type:Schema.ObjectId, ref: "Track"}],
disliked: [{type:Schema.ObjectId, ref: "Track"}],
later: [{type:Schema.ObjectId, ref: "Track"}]
}
});
But actually I'm feeling this is not the correct way of do it.
Is there a simpler or more correct way of query this?
I'm not sure what classifies as correct, but you can at least run the bulk of the query in mongodb without returning the entire collection.
Mongoose Query#distinct which is db.collection.distinct() will return distinct array items and can be supplied a query.
User.distinct('tracks.disliked', { username: { $ne: username } })
User.distinct('tracks.liked', { username: { $ne: username } })
This will give you the arrays for liked and disliked, which you can then difference for a user.
UserSchema.methods.itemsUserHasntQualified = function () {
var user = this
var liked = User.distinct('tracks.liked', { username: { $ne: user.username } })
var disliked = User.distinct('tracks.disliked', { username: { $ne: user.username } })
Promise.all([liked, disliked]).then(function (results) {
var all_ratings = _.union( results[0], results[1] )
var users_ratings = _.union( user.tracks.liked, user.tracks.disliked )
var missing = _.difference( users_ratings, all_ratings )
return missing
})
}
Depending on your access patterns, you might want to run this collection scan somewhere else, less frequently and cache the array results for use in itemsUserHasntQualified.

Express +Mongoose push object into array of objects and adding two more rows into the object while inserting

I want to add a user inside the array objects, and add two more rows while inserting.
These are the two mongoose models that are used.
module.exports = mongoose.model('Users', {
id:String, //the same id as user.id
nick:String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
user: [{
id: String,
nick: String,
nr: Number,
earnings: Number
}],
total: Number
});
So let's say I want to insert a Users that is found by its id(not the auto-generated). (I have the removed the if (err) to make it readable).
This how i try to solve right now.
Users.findOne({id : req.body.userid }, function(err, user) {
//what user contains
user = { _id: 551fb0b688eacdf0e700000c,
id: '123abc',
nick: 'Foo',
__v: 0 }
//I want to add this into the user and push it into exsisting array of
objects that is 'users'
//But before that i want to add more info to the user,
//the desired format that I want to insert into 'users'
user = {id: '123abc',
nick: 'Foo',
nr: req.body.nr, //new
earnings: req.body.earnings} //new
Stores.update({id: req.params.id},
{$push: { 'users' : user }}, function(err, store) {
});
});
The current result is the following.
users: [
{
id: "123abc",
nick: "Foo"
}]
How can I solve this?
The schema design as is creates at least one problem. What if a user updates their nick? Rather than only updating the Users collection you would also need to update every document in Stores that matches the user. You could use a ref and then populate to negate this concern.
module.exports = mongoose.model('Users', {
id: String, //the same id as user.id
nick: String, //the same nick as user.nick
});
module.exports = mongoose.model('Stores', {
id: String,
users: [{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
},
nr: Number,
earnings: Number
}],
total: Number
});
Now the query would be:
Users.findOne({
id: req.body.userid
}, function(err, user) {
Stores.update({
id: req.params.id
}, {
$push: {
'users': {
user: user,
nr: req.body.nr, //new
earnings: req.body.earnings
}
}
}, function(err, store) {});
});
Later when you need to query Stores:
Stores
.find(QUERY)
.populate('users')
.exec(function(err, stores) {...
});

Updating 2 mongoose schemas in an api call

Currently I'm trying to update Two different User Schema's in an api call.
The first schema is logged in user schema, we give it a name = Tom
The second schema is other users who signup for the app, we give it a name = John
The schema code
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
followersCount: Number,
followingCount: Number
});
module.exports = mongoose.model('User', UserSchema);
The api name is '/follow/:user_id', what I want to achieve is . Whenever user Tom follows other user's like John, Tom's following field will be updated as well as John's follower field.
My current attempt (req.decoded.id is the logged in user)
api.js
// The first way
apiRouter.post('/follow/:user_id', function(req, res) {
User.findOneAndUpdate(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err, currentUser) {
if (err) {
res.send(err);
return;
}
console.log(currentUser);
});
User.findOneAndUpdate(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}, function(err, user) {
if(err) {
res.send(err);
return;
}
res.json({
message: "Successfully followed"
});
}
)
});
//Second way
apiRouter.post('/follow/:user_id', function(req, res) {
// find a current user that has logged in
User.update(
{
_id: req.decoded.id,
following: { $ne: req.params.user_id }
},
{
$push: { following: req.params.user_id},
$inc: { followingCount: 1}
},
function(err) {
if (err) {
res.send(err);
return;
}
User.update(
{
_id: req.params.user_id,
followers: { $ne: req.decoded.id }
},
{
$push: { followers: req.decoded.id },
$inc: { followersCount: 1}
}
), function(err) {
if(err) return res.send(err);
res.json({ message: "Successfully Followed!" });
}
});
});
Both have problems,
The first way: The problem is, 'Can't set headers that already sent', because of the two separate mongoose query in one api call, it response twice that's why I get that error.
The second way: The problem is, the following field of logged in user(Tom) gets updated while the other user's followers field (John) return null. I console log both value and as well test it with POSTMAN chrome app.
Lend me your thoughts fellas!
The first route you took seems to be fine.
However, as #cdbajorin mentioned, the error "can't send headers that already sent" has nothing to do with mongoose but the fact that you're trying to set the header after sending a response to the client already. (see this lovely answer)
My suggestion would be to make sure that both database calls are successful before you send a response.
You may also want to look into a two phase commit in this situation, as MongoDB does not support traditional DB transactions and you're updating two documents, one at a time. If for some reason either database call fails, a procedure to recover to a stable state should be taken.
The first way can be improved in two ways. One is updating followers field inside the callback of updating following field. The other way is using async-waterfall. I suggest to go with async-waterfall(npm async-waterfall).
The second way it is correct (could be improved running both of them in parallel) I guess the problem is in another place. I don't know which framework you are using but i guess the field _id is from mongoDB and is an ObjectId and looks like that the decoded.id can be an objectId while the one that comes from the request is of course just a string. So I guess it is empty because it does not find any user with that string.
Try do make it an objectId out of that string ( reffering to req.params.user_id in the second query)

Mongoose populate list of references

Mongoose (v3.8) + Node.js
I have a model called 'Products' which has this field:
upvoted_by: [{
user_id: { type: Schema.ObjectId, ref: 'Users' },
timestamp: { type: Date }
}]
And the Users model has a bunch of other fields.
To add an upvote, I do this:
product.upvoted_by.push({
user_id: req.user,
timestamp: new Date()
});
I'm trying to populate the upvoted_by.user_id field so that it contains the corresponding user data.
I tried this but it doesn't seem to work:
// product (= a document) has been found before
product.populate('upvoted_by.user_id', {
select: 'username' // username is a field in the Users model
}, function(err, doc) {
console.log(JSON.stringify(doc));
});
Any idea what's going wrong and how to fix it?
As per the docs, the following syntax should work, where select is an attribute of the Object passed to the populate method.
product.populate({path:"upvoted_by.user_id",
select:"username"}).exec(function(err, doc) {
console.log(err);
console.log(JSON.stringify(doc));
});
I ended up doing this:
product.populate({
path: 'upvoted_by.user_id',
select: 'username profile.picture profile.name'}, function(err, doc) {
console.log(JSON.stringify(doc.upvoted_by));
});

Resources