Mongoose populate array of object ids - node.js

I'm trying to populate my Thread schemas GET response with all the comments related that that specific thread. I've specified a path within the Thread model to accept an array of Comments that I'll then populate when I request a thread, but it's continuing to be empty.
I'm not sure if I then need to push all the comments into the thread, but I think with the way I'm doing it, it's not required? I'm using Mongoose 4.4.19. I've followed along with the docs but still can't figure out where I've gone wrong.
Thread Schema:
const threadSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
title: {
type: String
},
content: {
type: String
},
category: {
type: String
},
comments: [{
type: Schema.ObjectId,
ref: 'Comment'
}]
}, {
timestamps: true
})
Comment Schema:
const commentSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: 'User',
required: true
},
thread: {
type: Schema.ObjectId,
ref: 'Thread',
required: true
},
content: {
type: String
}
}, {
timestamps: true
})
Handles get requests:
export const index = ({ querymen: { query, select, cursor } }, res, next) =>
Thread.find(query, select, cursor)
.populate('user comments')
.then(threads => threads.map(thread => thread.view()))
.then(success(res))
.catch(next)

Related

Populating Virtual Field in Mongoose

I'm currently building an application that logs poker statistics. Users create players and then populate games with those players.
I have a games model which references the players from the player model:
const gameSchema = new mongoose.Schema({
firstPlace: { type: mongoose.Schema.ObjectId, ref: 'Players', required: true },
secondPlace: { type: mongoose.Schema.ObjectId,ref: 'Players', required: true },
thirdPlace: { type: mongoose.Schema.ObjectId, ref: 'Players', required: true },
fourthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
fifthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
sixthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
seventhPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
eighthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
ninthPlace: { type: mongoose.Schema.ObjectId, ref: 'Players' },
buyIn: { type: Number, required: true },
firstPrize: { type: Number, required: true },
secondPrize: { type: Number, required: true },
thirdPrize: { type: Number, required: true },
date: { type: String },
notes: { type: String },
userId: { type: mongoose.Schema.ObjectId, ref: 'Users', required: true },
})
To get the information on a single game, I populate the references to players -
// Get single Game
async function getSingleGame(req, res, next) {
try {
const { gameId } = req.params
const foundGame = await Games.findById(gameId)
.populate('userId')
.populate('firstPlace')
.populate('secondPlace')
.populate('thirdPlace')
.populate('fourthPlace')
.populate('fifthPlace')
.populate('sixthPlace')
.populate('seventhPlace')
.populate('eighthPlace')
.populate('ninthPlace')
if (!foundGame) throw new NotFound()
return res.status(200).json(foundGame)
} catch (err) {
next(err)
}
}
This works well, so for example firstPlace is populated with an object that contains all the information of that player.
However, I have a virtual field on my user model which gets all the games that user has created. Code below -
userSchema
.virtual('addedGames', {
ref: 'Games',
localField: '_id',
foreignField: 'userId',
})
.get(function (addedGames) {
if (!addedGames) return
return addedGames.map((game) => {
return {
_id: game._id,
firstPlace: game.firstPlace,
secondPlace: game.secondPlace,
thirdPlace: game.thirdPlace,
fourthPlace: game.fourthPlace,
fifthPlace: game.fifthPlace,
sixthPlace: game.sixthPlace,
seventhPlace: game.seventhPlace,
eigthPlace: game.eigthPlace,
ninthPlace: game.ninthPlace,
buyIn: game.buyIn,
firstPrize: game.firstPrize,
secondPrize: game.secondPrize,
thirdPrize: game.thirdPrize,
}
})
})
This works - but all the placings are objectIds rather than the objects themselves. My question is - how can I populate these references to Players on this virtual field?
Thanks in advance, I've been pulling my hair out over this!
After much trial and error, I added this to populate the nested objects -
let user = await User.findById(currentUserId)
.populate('addedPlayers')
.populate([
{
path: 'addedGames',
populate: {
path: 'firstPlace secondPlace thirdPlace fourthPlace fifthPlace sixthPlace seventhPlace eighthPlace',
},
},
])
Originally I just had populate('addedGames')

mongoose populate nested schema without model

I'm trying to populate nested schemas without creating Model for the subschema, without any success.
I have a 'Question' Model, which created by 2 Schemas(Question, Option)
const Option = new mongoose.Schema({
value: { type: String, required: true }
})
const Question = new mongoose.Schema({
content: { type: String, required: true },
options: [Option]
})
module.exports = mongoose.model('Question', Question)
And I have a 'Review' Model
const Review = new mongoose.Schema({
results: [
{
question: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' },
option: { type: mongoose.Schema.Types.ObjectId, ref: 'Question.options' }
}
],
critical: { type: Boolean, default: false }
})
module.exports = mongoose.model('Review', Review)
Well, I want to create API /reviews that response array of review document, but populate the question and option.
I try this command but it isn't working.
Model.find({}).populate('results.option')
any idea?
as populate can't reference the array of subdocuments, as you have separate Option schema, why not use that.
const Review = new mongoose.Schema({
results: [
{
question: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' },
option: { type: mongoose.Schema.Types.ObjectId, ref: 'Option' }
}
],
critical: { type: Boolean, default: false }
})
module.exports = mongoose.model('Review', Review)

Mongoose's populate() does not return data from database

I have the following Schema, and Base is the one-for-all collector of info:
const BaseSchema = mongoose.Schema({
creatorId: { type: mongoose.Schema.Types.ObjectId, required: true },
title: { type: String, required: true },
created: { type: Date, default: Date.now() },
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: "Message" }]
});
And:
const BaseUserSchema = mongoose.Schema({
userId: { type: String },
baseId: { type: mongoose.Schema.Types.ObjectId },
created: { type: Date, default: Date.now() },
acceptedMembership: { type: Boolean, default: false },
isCreator: { type: Boolean, default: false }
});
(I have one for Message which looks about the same)
The latter one is referred to as User in const User = mongoose.model("User", UserSchema);
When I create a new Base I automatically add a user to a list within. In the DB i can see that the user does exist, but when I call the following the field does not populate:
Base.find({ creatorId: req.params.id })
.populate("users", "messages")
.exec()
.then(data => console.log(data));
I get the following from the console.log:
[ { created: 2018-09-05T03:41:45.416Z,
users: [],
messages: [],
_id: 5b8f508b2760c5329c13a9be,
creatorId: 5b86f7970cd98b2004969bf0,
title: 'testBase1',
__v: 1 } ]
When I first create the base via React front-end, and the base gets added to a list, I see that the users.length is 1, or the length of the automatically created user. When I refresh the page, however, the userlist is empty in the console.
Adding:
Forget to show how I populate the userlist upon creation:
router.post("/add", jwtAuth, (req, res) => {
Base.create({
creatorId: req.body.userId,
title: req.body.title
}).then(baseInfo => {
BaseUser.create({
userId: req.body.username,
baseId: baseInfo._id,
created: Date.now(),
acceptedMembership: true,
isCreator: true
})
.then(baseuser => {
baseInfo.users.push(baseuser);
return baseInfo.save();
})
.then(base => res.json(base.serialize()));
});
});
Answer:
.populate("users", "messages")
to begin with will return messages from users. It has to be two separate entries, like so:
.populate("users")
.populate("messages")
Then, in the model
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
refers to the wrong ref. It has to be
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "BaseUser" }],
from
const BaseUser = mongoose.model("BaseUser", BaseUserSchema);
At last, I am not sure if this is needed, but I added a ref to each of the to-be-populated items:
baseId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: "Base" },
baseId: { type: mongoose.Schema.Types.ObjectId, ref: "Base" },
Now it works like a charm, all without additional fetch

Mongoose query compare ObjectId

I'm trying to fetch some documents from my db. In each document, there is a field called 'owner' which is an ObjectId of a user. I want to fetch all of the documents of a specific user. I have the user id and when I'm trying to do something like this:
exports.getBoxes = function(req, res) {
const { user } = res.locals;
const query = db.Box.find();
query.where('owner').equals(user._id);
query.exec(function(err, boxes) {
console.log(boxes);
});
}
I get an empty array. I saw in my db and there are many boxes that corresponds to this query. What's wrong with it?
UPDATE
Here is my schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const timestamps = require('mongoose-timestamps');
const BoxSchema = new Schema({
description: {
type: String,
trim: true
},
producer: {
type: String,
trim: true
},
cycle: {
type: String,
trim: true
},
owner: {
type: Schema.ObjectId,
ref: 'Supplier'
},
event: {
type: Schema.ObjectId,
ref: 'Event'
},
type: {
type: String,
enum: []
},
creditTerms: {
type: String,
enum: ['Cash', '30 Days', '60 Days', '90 Days', '120 Days']
},
bids: [{
type: Schema.ObjectId,
ref: 'Bid'
}],
looking: [{
type: Schema.ObjectId,
ref: 'User'
}],
sold: Boolean,
paid: Boolean,
delivered: Boolean,
sealed: Boolean,
initialPrice: Number,
value: Number,
cts: Number,
ppc: Number,
finalPrice: Number
});
BoxSchema.plugin(timestamps);
module.exports = mongoose.model('Box', BoxSchema);
And here is an example of documents that I try to fetch:
https://i.gyazo.com/38f2d16d6831b831adb3cc448ef74d01.png
Okay guys I managed to solve this problem. The problem was that the owner field in the box schema referenced a Supplier object, not a User object. So I solved it like so:
const { user } = res.locals;
return db.Supplier.findOne({ userId: user._id })
.populate('boxes').exec(function(err, supplier) {
if(err || !supplier) return res.sendStatus(404);
res.json(supplier.boxes);
});

Find all the documents that has same subdocuments mongoose

I have postSchema which references the tagsSchema.
var tagsSchem = new Schema({
name: {
type: String,
required: true
}
}, {
timestamps: true
});
// create a schema
var postsSchema = new Schema({
title: {
type: String,
required: true,
unique: true
},
mainImage: {
type: String
},
category: {
type: String,
required: true
},
body: {
type: String,
required: true
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
tags: [tagsSchem]
}, {
timestamps: true
});
One post can contain any no. of tags. So if a post has 3 tags then I want to get all the posts with those 3 tags without querying it multiple times. Is it possible?
When you perform find, you can use the $in option to find values that are in your array. For example:
posts.find({tags:{$in:{["tag1","tag2","tag3"]}}, function(err,data) {
... //Your code here
}
This will take all the posts that contains one of the three tags. It's important you have to pass an array in the $in option. This should work.

Resources