How to acess Subdocuments with reference in Mongoose? - node.js

I want to acess Subdocument using Mongoose
This is my conversations schema :
const Conversations = new mongoose.Schema({
userOneId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
userTwoId: {
type: Schema.Types.ObjectId,
ref: 'User'
}
This is my User Model Schema:
....
conversations: [{ type: Schema.Types.ObjectId, ref: 'Conversations' }]
});
After inserted i get this:
{
"_id": {
"$oid": "5a6fa114ffc53523705d52af"
},
"created_at": {
"$date": "2018-01-29T22:32:52.930Z"
},
"messages": [],
"__v": 0
}
I have inserted this :
"conversations": [
{
"$oid": "5a6fa14a5562572a541bacae"
},
I have put this:
Object.assign(conversation, {userOneId: user._id});
Object.assign(conversation, {userTwoId: friend._id});
I want to acess the "$oid": "5a6fa114ffc53523705d52af" to get the userOneId and userTwoId information.

you need to use populate.
Basically, inside the "conversations" property in a user object, you only have the ObjectId of conversations, not the entire mongoose object. when you're querying the database for a user/users, you have to tell mongoose that you want it to replace the ObjectIds with the entire objects.
//lets say you have a variable, userId, inside of which you have the id
//of a user, and now you're querying the database for that user
User.findById(userId).populate("conversations")
.exec(function(err, foundUser){
if(err){
console.log(err):
} else {
console.log(foundUser.conversations);
}
});
if you were to run the code above with an actual user _id, then (unless you got an error) you would have printed in your console rather than an array of conversation mongoose ids, an array of conversation mongoose objects. the entire things.
if you only wanted the conversations to have two properties, userOneId and userTwoId, then combine populate with select. instead of
.populate("conversations")
use
.populate({
path: "conversations",
select: "userOneId userTwoId"
})

Related

How to get categories and sub-categories in single API response

I have two collections Categories and Subcategories inside a Categories collection. I have an array that is storing the ids of subcategories that are stored in Subcategories collection. Below is my document structure:
Categories collection
{
id:65,
title:"Automotive",
active:true,
subcategories:[35,28,30]
}
subcategories collection
{
id:35,
title:"Automotive technology",
category_id:65,
active:true
},
{
id:28,
title:"Automotive coatings",
category_id:65,
active:true
},
{
id:30,
title:"Machinery and equipments",
category_id:65,
active:true
}
As seen in above collection 3 documents from subcategories collection have been associated with the category document. I want to fetch data in below format on single API hit.
API response should be in below format:
{
data:{
category:{
id:65,
title:"Automotive",
subcategories:[{
id:35,
name:"Automotive technology",
},
{
id:28,
name:"Automotive coatings",
},
{
id:30,
name:"Machinery and equipments",
}]
},
category:{
id:66,
title:"Food",
subcategories:[{
id:23,
name:"Drinks",
},
{
id:2,
name:"Additives",
}]
},
},
messsage:"Success",
code:200
}
As of now I am able to get data in 2 api hits that is like first getting all the categories
const category = await db.categories.find({});
Then on click of some particular category fetching all the sub categories based on the category id.
const subCategories = await db.SubCategories.find({category_id:id});
How can I get the above data in desired format in single API hit?
You need something like this, also, if you use mongoose, you can use .populate()
To format data you can use $project stage in aggregation pipeline or projection in .find()
If you want to use Mongoose with populate:
CategorySchema:
const CategorySchema= new mongoose.Schema({
...
subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'SubCategory' }],
...
});
need _id column on reference table
ref content is must be equal to model name like
module.exports = mongoose.model('SubCategory', SubCategorySchema);
Controller:
const categories = await Category.find({})
.populate({
path: 'subCategories'
})
path content is must be equal to column name
If you want to write with Mongo query:
db.getCollection("categories").aggregate([
{
$lookup:
{
from: 'subCategories',
localField: 'subCategories',
foreignField: 'id',
as: 'subCategories'
}
}
])
You get empty object because you are not using "_id" field for join schemas. If you want to use "id" field u need to use virtuals on Mongoose:
https://mongoosejs.com/docs/tutorials/virtuals.html#populate

Search and lookup arrays nested in multiple objects

I find all websites that match the IDs I have in my array and in the below case it is two websites. I then want to look inside each of the conversations arrays of those websites and search a different collection called conversations for conversations that match those IDs. I then want to grab some/all of the fields from those conversations and add it to the JSON document I already have, grouping them by conversation within each website. I've been playing around with aggregate, unwind, and group but can't seem to get anything to work.
router.post('/staffLoadUpTwo', async (req, res) => {
var userId = '5e8f964a9c2d0780c0163825';
const company = await Company.findOne({ 'roles.admins': userId });
var unwrappedCompany = JSON.parse(JSON.stringify(company));
console.log(unwrappedCompany.websites);
const website = await Website.find({
_id: { $in: unwrappedCompany.websites },
});
// const unwindTest = await Website.aggregate([{$unwind : "$conversations"}]);
console.log(website);
});
console.log(website);
[ { conversations: [ '5e90d9ceb089812c9ba1a67b', '5e8f5a6a2582bf629998c3fd' ],
_id: 5e949cc02483c0c0056a1a98,
domain: 'http://x.com',
__v: 0 },
{ conversations: [ '5e8e23595ce6d611cec5033f', '5e8e3afee8e95e1ff94650d3' ],
_id: 5e949ce8f53450c0341b36cd,
domain: 'http://y.com',
__v: 0 } ]
ideal output
[{
_id: "5e949cc02483c0c0056a1a98",
domain: 'http://x.com'
conversations: [
{conversationId: "5e90d9ceb089812c9ba1a67b", messages: {messageArray: ['a'], timeSent: 2}},
{conversationId: "5e8f5a6a2582bf629998c3fd", messages: {messageArray: ['b'], timeSent: 6}}
]
}
_id: "5e949ce8f53450c0341b36cd",
domain: 'http://y.com'
conversations: [
{conversationId: "5e8e23595ce6d611cec5033f", messages: {messageArray: ['c'], timeSent: 1}},
{conversationId: "5e8e3afee8e95e1ff94650d3", messages: {messageArray: ['d'], timeSent: 8}}
]
}]
You need not stress yourself with MongoDB aggregation. Since you're using Mongoose, you can easily use mongoose populate to achieve the result you described in the question.
Provided you've defined the website scheme to be something like this:
const websiteSchema = {
// ...schema definition for other properties
/* Note the ref property used below, the value must be the name of the
conversation model, i.e the stringValue you passed into
mongoose.model(<stringValue>, conversationSchema); */
conversations: [ { type: mongoose.Types.ObjectId, ref: 'Conversations' } ]
}
A mongoose query like this:
const website = await Website.find({
_id: { $in: unwrappedCompany.websites },
}).populate('conversations');
will output an array of website documents whose conversations field are populated i.e, you get the actual conversation document and not just their _ids.
More about Mongoose populate here

Can't push items in mongo array

I can't push items into MongoDB array every time that i try to push a new element it creates an empty object and i cant figure out why,
I already used the
Collection.Array.push({element})&
Collection.save()
but i cant figure out a solution
This is My Schema
const Schema = mongoose.Schema;
var ParticipantSchema = new Schema({
nom:{Type:String},
prenom:{Type:String},
email:{Type:String}
})
var CompetitionSchema = new Schema({
nom:String,
date:Date,
place:String,
participant :[ParticipantSchema]
})
module.exports = mongoose.model("Competition",CompetitionSchema);
This is my funtion
exports.addParticipant=function(req,res){
var newParticipant={
"nom":req.body.nom,
"prenom":req.body.prenom,
"email":req.body.email
}
Competition.updateOne(
{ _id:req.body.id},
{ $push: { participant: newParticipant } },
(err,done)=>{
return res.json(done)
}
);
}
the result is always an empty object like below
{
"_id": "5ded0eeb85daa100dc5e57bf",
"nom": "Final",
"date": "2019-01-01T23:00:00.000Z",
"place": "Sousse",
"participant": [
{
"_id": "5ded0eeb85daa100dc5e57c0"
},
{
"_id": "5dee3c1b08474e27ac70672e"
}
],
"__v": 0
}
There is no problem in your code, the only problem is that in schema definition you have Type, but it must be type.
If you update your ParticipantSchema like this, it will work:
var ParticipantSchema = new Schema({
nom: { type: String },
prenom: { type: String },
email: { type: String }
});
You are using another Schema in the Array. This results in so-called subdocuments (https://mongoosejs.com/docs/subdocs.html). Mongoose does not populate subdocuments by default. So all you see is just the _id. You can use the populate method to see all subdocuments in detail. ( https://mongoosejs.com/docs/populate.html ) .
Example :
Competition.
find({}).
populate('participant').
exec(function (err, comps) {
//
});
You can either use populate on the Model or on the Document. For populating a document, take a look at https://mongoosejs.com/docs/api.html#document_Document-populate . There is also a auto-populate plugin available via npm but in most cases it's not necessary : https://www.npmjs.com/package/mongoose-autopopulate .

MongoDB Searching if element exist in array

So I have something like Survey Schema (I am using mongoose).
In this Schema, for each voting option, I have votes[] array that contains ObjectIds of Users.
Now I want to check if User can vote again, or if he already voted?
The simple solution is iterating thru votes with .indexOf() and checking if id exists. Now this is a blocking way for Node JS, since this operation is sync.
Is there a way to do this with Mongo aggregate or query? So for each voting option I would get additional field like:
didIVoted: true
My Schema looks like this:
const SurveySchema = new Schema({
title: {
type: String
},
options: [{
value: String,
votes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }]
}]
}, { timestamps: true })
You can use $addFields and $map to overwrite existing options field. To check if userId exists in votes array you can use $indexOfArray
SurveySchema.aggregate([
{
$addFields: {
options: {
$map: {
input: "$options",
in: {
value: "$$this.value",
votes: "$$this.votes",
didIVote: { $ne: [ { $indexOfArray: [ "$$this.votes", userId ] }, -1 ] }
}
}
}
}
}
])

Mongoose findOne returns empty array field

I have a simple embedded document:
{
"username":"user001",
"name":"John",
"tasks":[
{
"id":0,
"title":"Candy",
"description":"Lots of candy for you",
"category":"food",
"cost":2500,
"candyTypes":[
{"name":"gum", "type":"sweet", "price":"2"},
{"name":"chocolate", "type":"tasty", "price":"3"}
]
}
]
}
When I try to query the task data through the mongo shell, I get everything:
db.users.findOne({ 'username': 'user001', 'tasks.id':4 }, {'tasks.$':1})
/* returns */
"tasks":[
{
"id":0,
"title":"Candy",
"description":"Lots of candy for you",
"category":"food",
"cost":2500,
"candyTypes":[
{"name":"gum", "type":"sweet", "price":"2"},
{"name":"chocolate", "type":"tasty", "price":"3"}
]
}
]
But when I try to do the same in mongoose, the candyTypes array comes back empty:
Users.findOne({ 'username': username, 'tasks.id':taskId }, {'tasks.$':1}, function (err, data) {
console.log(data);
});
/* returns */
"tasks":[
{
"id":0,
"title":"Candy",
"description":"Lots of candy for you",
"category":"food",
"cost":2500,
"candyTypes":[]
}
]
I'm pretty new to MongoDB and Mongoose, but after searching and looking through documentation, I can't figure out what I'm missing.
UPDATE
I couple users requested it, so here is my mongoose schema:
var UserSchema = new mongoose.Schema({
username:String,
name:String,
tasks:[{
id: Number,
title: String,
description:String,
category: String,
cost: Number,
candyTypes:[{
title:String,
type:String,
value:String
}]
}]
});
With Mongoose, you have to populate candyTypes array:
Users.findOne({ 'username': username, 'tasks.id':taskId }, {'tasks.$':1})
.populate('candyTypes')
.exec(function (err, data) {
console.log(data);
});
See docs: http://mongoosejs.com/docs/populate.html
I think it's related to you declaring a field called type which also has a special meaning in Mongoose, namely to signify the type of a field.
If you rename that field to something else (candyType), it'll probably work better.
Alternatively, you can use the typeKey option to make Mongoose use a different property name to signify field type.
As an aside, your document contains a field price but your schema names it value.

Resources