Populating nested array with ObjectIDs - mongodb

Trying to populate an array of ObjectID's within my schema. I've looked around at similar answers but it seems everyone is doing it slightly differently and I haven't been able to find a solution myself.
My schema looks like this:
var GameSchema = new Schema({
title: String,
description: String,
location: String,
created_on: { type: Date, default: Date.now },
active: { type: Boolean, default: true },
accepting_players: { type: Boolean, default: true },
players: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
admins: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
});
So far I've been trying to populate it like this, which obviously isn't working
exports.getAdmins = function(req, res) {
Game.findById(req.params.id)
.populate('admins')
.exec(function(err, game) {
return res.json(200, game.admins);
});
};
I hate to add to the list of population questions, but I've looked at many and haven't found a solution. Any help is greatly appreciated!
Edit:
Here's how I am adding admins to the document
// Add admin to game
exports.addAdmin = function(req, res) {
Game.findByIdAndUpdate(
req.params.id,
{ $push: { 'admins': req.params.user_id }},
function(err, game) {
if(err) { return handleError(res, err); }
if(!game) { return res.send(404); }
return res.json(200, game.admins);
});
};

Well I went back to mongoose documentation, and decided to change how I looked up a game by an ID and then populated the response.
Now my working function looks like this:
// Returns admins in a game
exports.getAdmins = function(req, res) {
Game.findById(req.params.id, function(err, game) {
if(err) { return handleError(res, err); }
if(!game) { return res.send(404); }
Game.populate(game, { path: 'admins' }, function(err, game) {
return res.json(200, game);
});
});
};
The issue I was having was that I was trying to call the .populate function directly with the .findById method, but that doesn't work because I found on mongoose's documentation the the populate method need the callback function to work, so I just added that and voila, it returned my User object.

to populate an array, you just have to put model name field after path field like this :
Game.findById(req.params.id)
.populate({path: 'admins', model: 'AdminsModel'})
.exec(function(err, game){...});
it works perfectly on my projects...

Related

Can't add array to mongodb

I'm trying to send an array to mongodb, but the res.json(user) returns an empty biddingGroup:[] and mongodb document never has field biddingGroup appear. I've looked at stack posts and have seen suggestions for schema.
I've tried
biddingGroup: [{type: String}],
biddingGroup: [String],
biddingGroup: {type: String},
I haven't found a working schema that captures the data yet.
I even hardcoded biddingGroup: ['test'] too, but it never shows up.
app.js
app.put('/api/listings/:id', (req, res) =>
Post.update({
id: req.query.id
}, {
$set: {
currentBid: req.query.currentBid,
lastBidTimeStamp: req.params.lastBidTimeStamp,
biddingGroup: ['test']
}
}, {
multi: false //set to false to ensure only one document gets updated
}).exec().then(data => {
console.log(data);
}, err => {
console.log(err);
})
);
Any help is appreciated.
You need to use exec() at the end to run the query. That is the function that actually runs the request and returns you the promise. Plus your usage of the update function in general is off.
Try this:
Post.update({
id: req.query.id
}, {
$set: {
currentBid: req.query.currentBid,
lastBidTimeStamp: req.params.lastBidTimeStamp,
biddingGroup: ['test']
}
}, {
multi: false //set to false to ensure only one document gets updated
}).exec().then(data => {
console.log(data);
}, err => {
console.log(err);
});

Update query adding ObjectIDs to array twice

I am working on a table planner application where guests can be assigned to tables. The table model has the following Schema:
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const tableSchema = new mongoose.Schema({
name: {
type: String,
required: 'Please provide the name of the table',
trim: true,
},
capacity: {
type: Number,
required: 'Please provide the capacity of the table',
},
guests: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Guest',
}],
});
module.exports = mongoose.model('Table', tableSchema);
Guests can be dragged and dropped in the App (using React DND) to "Table" React components. Upon being dropped on a table, an Axios POST request is made to a Node.js method to update the Database and add the guest's Object ID to an array within the Table model:
exports.updateTableGuests = async (req, res) => {
console.log(req.body.guestId);
await Table.findOneAndUpdate(
{ name: req.body.tablename },
{ $push: { guests: req.body.guestId } },
{ safe: true, upsert: true },
(err) => {
if (err) {
console.log(err);
} else {
// do stuff
}
},
);
res.send('back');
};
This is working as expected, except that with each dropped guest, the Table model's guests array is updated with the same guest Object ID twice? Does anyone know why this would be?
I have tried logging the req.body.guestID to ensure that it is a single value and also to check that this function is not being called twice. But neither of those tests brought unexpected results. I therefore suspect something is wrong with my findOneAndUpdate query?
Don't use $push operator here, you need to use $addToSet operator instead...
The $push operator can update the array with same value many times
where as The $addToSet operator adds a value to an array unless the
value is already present.
exports.updateTableGuests = async (req, res) => {
console.log(req.body.guestId);
await Table.findOneAndUpdate(
{ name: req.body.tablename },
{ $addToSet : { guests: req.body.guestId } },
{ safe: true, upsert: true },
(err) => {
if (err) {
console.log(err);
} else {
// do stuff
}
},
);
res.send('back');
};
I am not sure if addToSet is the best solution because the query being executed twice.
If you used a callback and a promise simultaneously, it would make the query executes twice.
So choosing one of them would make it works fine.
Like below:
async updateField({ fieldName, shop_id, item }) {
return Shop.findByIdAndUpdate(
shop_id,
{ $push: { menuItems: item } },
{ upsert: true, new: true }
);
}

Populate does not retrieve the whole referenced object just the ids

I've been reading a few answers regarding this and yet I still can't get it to work.
My model objects aren't deeply nested and are quite simple. It's events that have a list of users attending them and users that have a list of events they've attended. like so:
let DinnerSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title:{type: String, require: true},
attending: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
and the users:
let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{ type: String, require: true },
password: {type: String ,required: true},
dinners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Dinner'
}]
})
And for clarity here's the entire route that's using populate:
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('User', 'name') //I'VE TRIED ADDING 'name' BASED ON SOME ANSWERS I SAW
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
If I understand correctly in the database itself there should only be a reference to the other model and not actually all of it's fields and the join happens with the populate. My db structure show's just the reference so that's ok.
I've tried specifying the name of the fields i'm after (the name field in this case) but that didn't work.
My population result always looks like the following and doesn't show any other fields except for the _id one:
{
_id: 57ebbf48bd6914036f99acc7,
date: 2016-09-27T22:00:00.000Z,
title: '1',
__v: 0,
attending: [ 57ebbcf02c39997f9cf26891, 57ebbdee098d3c0163db9905 ]
}
What am I screwing up here?
In mongoose populate receives 4 parameters.
path
selection(fields to be return) ,
condition
options (like {limit:10})
In your case you are not passing right path to populate. It should be
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('attending', 'name')
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
Now it will return all the names of attending users.
you need to populate attending - that's your user reference in the dinner schema

Document is not being saved in database

In Article model, I want to save a list of categories:
var CategorySchema = new Schema({
name: String,
active: Boolean
});
var ArticleSchema = new Schema({
title: String,
description: String,
categories: [{ type : Schema.Types.ObjectId, ref: 'Category' }]
})
From the endpoint, I want to update article with categories. The update method looks like:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Article.findById(req.params.id, function (err, article) {
if (err) { return handleError(res, err); }
if(!article) { return res.send(404); }
var updated = _.merge(article, req.body);
updated.save(function (err, doc) {
console.log(doc);
if (err) { return handleError(res, err); }
return res.json(200, article);
});
});
};
Notice the console.log statement. In request.body, if I'm sending list of category ids, the console prints out an article with categories. However, when I look into the database, the category array is empty. Any pointer to how to solve this?

How to update embedded document in mongoose?

I've looked through the mongoose API, and many questions on SO and on the google group, and still can't figure out updating embedded documents.
I'm trying to update this particular userListings object with the contents of args.
for (var i = 0; i < req.user.userListings.length; i++) {
if (req.user.userListings[i].listingId == req.params.listingId) {
User.update({
_id: req.user._id,
'userListings._id': req.user.userListings[i]._id
}, {
'userListings.isRead': args.isRead,
'userListings.isFavorite': args.isFavorite,
'userListings.isArchived': args.isArchived
}, function(err, user) {
res.send(user);
});
}
}
Here are the schemas:
var userListingSchema = new mongoose.Schema({
listingId: ObjectId,
isRead: {
type: Boolean,
default: true
},
isFavorite: {
type: Boolean,
default: false
},
isArchived: {
type: Boolean,
default: false
}
});
var userSchema = new mongoose.Schema({
userListings: [userListingSchema]
});
This find also doesn't work, which is probably the first issue:
User.find({
'_id': req.user._id,
'userListings._id': req.user.userListings[i]._id
}, function(err, user) {
console.log(err ? err : user);
});
which returns:
{ stack: [Getter/Setter],
arguments: [ 'path', undefined ],
type: 'non_object_property_call',
message: [Getter/Setter] }
That should be the equivalent of this mongo client call:
db.users.find({'userListings._id': ObjectId("4e44850101fde3a3f3000002"), _id: ObjectId("4e4483912bb87f8ef2000212")})
Running:
mongoose v1.8.1
mongoose-auth v0.0.11
node v0.4.10
when you already have the user, you can just do something like this:
var listing = req.user.userListings.id(req.params.listingId);
listing.isRead = args.isRead;
listing.isFavorite = args.isFavorite;
listing.isArchived = args.isArchived;
req.user.save(function (err) {
// ...
});
as found here: http://mongoosejs.com/docs/subdocs.html
Finding a sub-document
Each document has an _id. DocumentArrays have a special id method for looking up a document by its _id.
var doc = parent.children.id(id);
* * warning * *
as #zach pointed out, you have to declare the sub-document's schema before the actual document 's schema to be able to use the id() method.
Is this just a mismatch on variables names?
You have user.userListings[i].listingId in the for loop but user.userListings[i]._id in the find.
Are you looking for listingId or _id?
You have to save the parent object, and markModified the nested document.
That´s the way we do it
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Profile.findById(req.params.id, function (err, profile) {
if (err) { return handleError(res, err); }
if(!profile) { return res.send(404); }
var updated = _.merge(profile, req.body);
updated.markModified('NestedObj');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, profile);
});
});
};