Async: Combining two mongodb collection using Async.forEach - node.js

I am very new to MEAN stack and i am trying to learn async.
I'm trying to combine two collections from the mongodb using async
and applied this iterate over a collection, perform an async task for each item, i'm a trying to learn the simplest and efficient way of doing this simple tasks so it will be easy to understand.
var OrderSchema = new mongoose.Schema({
menu_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Foods'},
menu_name: {type:String,required:false},
customer_id: {type:String,required: true,},
customer_name:{type:String, required: false},
table_no:{type:String,required:true},
cooking:{type:Boolean, require:false, default:false},
ready:{type:Boolean,default:false},
served:{type:Boolean,default:false},
paid:{type:Boolean, default:false},
price: {type:Number, default:0},
quantity: {type:Number,default:0},
created_at: { type: Date, default: Date.now }
}
Payment Model
var mongoose = require('mongoose');
var PaymentSchema = new mongoose.Schema({
order_number: {type:String, required: true, index: true},
order_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Orders'},
date: { type: Date, default: Date.now },
customer_id: {type:mongoose.Schema.Types.ObjectId, ref: 'User'},
amount : { type: Number, required:true },
company_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Company'}
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
module.exports = mongoose.model('Payments', PaymentSchema);
Here is my Code
var data = req.body;
var calls = [];
var local_orders = [];
var OrderModel = require('../models/Order');
var PaymentModel = require('../models/Payment');
OrderModel.find({'table_no': data.table_no}, function(err,orders){
async.forEach(orders, function(vorders, callback){
PaymentModel.find({order_id:vorders.id}, function(err, payments){
vorders.payments = 'payments';
local_orders.push(vorders)
});
return callback(null, local_orders);
}, function(err,local_orders){
if(err){
res.status('500').send(err);
}
res.send(local_orders)
});
})
I am expecting to receive a JSON Object like this, but i'm getting is undefined.
[{ menu_id: {type:mongoose.Schema.Types.ObjectId, ref: 'Foods'},
menu_name: {type:String,required:false},
user_id: {type:String,required: true,},
customer_name:{type:String, required: false},
table_no:{type:String,required:true},
cooking:{type:Boolean, require:false, default:false},
ready:{type:Boolean,default:false},
served:{type:Boolean,default:false},
paid:{type:Boolean, default:false},
price: {type:Number, default:0},
quantity: {type:Number,default:0},
created_at: { type: Date, default: Date.now },
payments : [{ payment1 },{ payment2 }
},...]
Please comment if you need more clarification or something is missing. Thank you! Cheers!

The simplest and most efficient way of doing this simple task is by using the aggregation framework where you can leverage mongo's native operators like $match to filter the document stream to allow only matching documents to pass unmodified into the next pipeline stage and $lookup to perform a left outer join to the payment collection in the same database to filter in documents from the "joined" collection for processing:
var data = req.body;
OrderModel.aggregate([
{ "$match": { "table_no": data.table_no } },
{
"$lookup": {
"from": "payments",
"localField": "_id",
"foreignField": "order_id",
"as": "payments"
}
}
]).exec(function (err, result){
if (err){
res.status('500').send(err);
}
res.send(result)
});
However, as it stands your code is failing here
PaymentModel.find({ order_id: vorders.id }, function(err, payments){
since vorders object does not have any id key but _id, so that should be
PaymentModel.find({ "order_id": vorders._id }, function(err, payments){

Related

How do I find object based on child populated property on mongoose

I'm new in mongoose.
I have a Schema like this:
const sessionSchema = mongoose.Schema({
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
registers: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Register',
},
],
date: {
type: Date,
default: Date.now,
immutable: true,
},
});
And register model is this one:
const registerSchema = mongoose.Schema({
sets: [
{
weight: {
type: Number,
},
weightUnit: {
type: String,
default: 'kg',
},
repetitions: {
type: Number,
},
duration: {
type: Number,
},
},
],
session: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Session',
},
exercise: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Exercise',
},
creationDate: {
type: Date,
default: Date.now,
immutable: true,
},
});
I want to find all the sessions created by a user and has an register with an specificied exercise id. I tried this:
const result = Session.find({createdBy: userId, 'registers.exercise': exerciseId}).populate('registers');
But doesn't work. ¿Any suggestion?
Thanks :P
Try this:
Session.aggregate([
{
$match: { createdBy: userId }
},
{
$lookup: {
from: 'Register',
localField: 'registers',
foreignField: '_id',
as: 'registers'
}
},
{
$match: { 'registers.exercise': exerciseId }
}
])
.then((result) => {
console.log('Result: ', result);
})
.catch((err) => {
console.log('Error: ', err);
});
If you are not familiar with MongoDB aggregation framework, the query above might be a bit foreign to you. However, for cross collection queries like you described in your question, the aggregation framework is your best shot(for now).
The $match is basically doing what you would do with a Model.find(), while the $lookup is doing what you would do with a Model.<query>.populate()
You can read more about MongoDB aggregation framework here.

Populating array of collection which contains references to another collections returns empty array

I have two models Vote and Link,I am trying to populate the votes array in link model,The votes array contains id's that references to the collection Vote,which only contains two fields link and User which also refs to same link model mentioned below and a user model respectively
link Schema:-
const linkSchema = new mongoose.Schema(
{
description: {
type: String,
trim: true,
},
url: {
type: String,
trim: true,
},
postedBy: {
type: mongoose.Types.ObjectId,
ref: "User",
},
votes: [{ type: mongoose.Types.ObjectId, ref: "Vote" }],
},
{
timestamps: true,
}
);
linkSchema.index({ description: "text" });
linkSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Link", linkSchema);
Vote schema:-
const mongoose = require("mongoose");
const voteSchema = new mongoose.Schema({
link: { type: mongoose.Types.ObjectId, ref: "Link" },
user: { type: mongoose.Types.ObjectId, ref: "User" },
});
module.exports = mongoose.model("Vote", voteSchema);
but when i try to get the votes of a link,it always return an empty array ,My function:-
const votes = async ({ id }) => {
const linkData = await Link.findById(id).populate("votes").exec();
console.log(linkData);
};
Output Data:-
{
votes: [], //empty always
_id: 5ecb21059a157117c03d4fac,
url: 'https://www.apollographql.com/docs/react/',
description: 'The best GraphQL client for React',
postedBy: 5ec92a58bf38c32b38400705,
createdAt: 2020-05-25T01:36:05.892Z,
updatedAt: 2020-05-25T01:37:52.266Z,
__v: 0
}
Instead of populate(), you can use aggregate() to get your desired output. This should probably work in your case:
Link.aggregate([
{
$match: {
_id: { $in: [mongoose.Types.ObjectId(id)] // as suggested by the questioner
}
},
{
$lookup: {
from: "vote", // collection to join
localField: "votes", // field from the input documents (filtered after _id is matched)
foreignField: "link", // field to compare with, from other collection
as: "linkData" // output array name
}
}
])
Let me know in the comments.

Mongoose Add New Field To Collection (Node.js)

I have a schema:
var userSchema = new Schema({
name: String,
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
admin: Boolean,
created_at: Date,
updated_at: Date
});
Let's assume I have made 100 Users using this schema.
Now I want to change the schema:
var userSchema = new Schema({
name: String,
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
admin: Boolean,
created_at: Date,
friends: [Schema.Types.ObjectId], //the new addition
updated_at: Date
});
I need all new Users to have this field. I also want all of the 100 existing Users to now have this field. How can I do this?
You can use Mongoose Model.update to update all your documents in the collection.
User.update({}, { friends: [] }, { multi: true }, function (err, raw) {
if (err) return handleError(err);
console.log('The raw response from Mongo was ', raw);
});
I don't recommend to do it in production if the collection is big, since it is a heavy operation. But in your case it should be fine.
Using the query interface in a client app or your terminal you could do:
db.users.updateMany({
$set: { "friends" : [] }
});
Here's the docs reference.
it doesn't work for me :x
Here is my code
let test = await this.client.db.users.updateMany({
$set: { "roles" : [] }
});
and the output
{ ok: 0, n: 0, nModified: 0 }
I don't know how to do, i tried a lot of things and uh it doesn't work :'(
EDIT: I found, here is my code
await this.client.db.users.updateMany({ }, [ {$set : { "roles": []} } ]);

Populate nested array in mongoose - Node.js

These are my schemas (Topic is parent and contains a list of 'Thought's):
var TopicSchema = new mongoose.Schema({
title: { type: String, unique: true },
category: String,
thoughts: [ThoughtSchema]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
var ThoughtSchema = new mongoose.Schema({
text: String,
author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
votes:[{
_id:false,
voter: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
up: Boolean,
date: {type: Date, default: Date.now}
}]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
....
I am trying to read the thought's author and change my get Topic api like this:
...
var cursor = Topic.find(query).populate({
path: 'thoughts',
populate: {
path: 'author',
model: 'User'
}
}).sort({popularity : -1, date: -1});
return cursor.exec()
.then(respondWithResult(res))
.catch(handleError(res));
...
But the author is null.. i also do not get any error in the console. What is wrong here?
Edit: Actually i do not need the Thought as a schema, it does not have its own collection in database. It will be saved in topics. But in order to use timestamps option with thoughts, i needed to extract its contents to a new local schema ThoughtSchema. But i have now defined the contents of thoughtSchema directly in the thoughts array of topics, it still does not work.
Edit2: This is the cursor object just before it is executed. Unfortunately i cannot debug in Webstorm, this is a screenshot from node inspector:
Did you try using Model.populate?
Topic.find(query).populate('thoughts')
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
// Multiple population per level
if(err) return callback(err);
Thought.populate(docs, {
path: 'thoughts.author',
model: 'User'
},
function(err, populatedDocs) {
if(err) return callback(err);
console.log(populatedDocs);
});
});
UPDATE:
You can try with deep populate like this:
Topic.find(query).populate({
path: 'thoughts',
populate: {
path: 'author',
model: 'User'
}
})
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
if(err) return callback(err);
console.log(docs);
});
How about
Topic.find(query).populate('thoughts')
.sort({popularity : -1, date: -1})
.exec(function(err, docs) {
// Multiple population per level
if(err) return callback(err);
Topic.populate(docs, {
path: 'thoughts.author',
model: 'User'
},
function(err, populatedDocs) {
if(err) return callback(err);
console.log(populatedDocs);
});
});
These are the schemas :
var TopicSchema = new mongoose.Schema({
title: { type: String, unique: true },
category: String,
thoughts: [ThoughtSchema]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
var ThoughtSchema = new mongoose.Schema({
text: String,
author: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
votes:[{
_id:false,
voter: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
up: Boolean,
date: {type: Date, default: Date.now}
}]
}, {
timestamps: true,
toObject: {virtuals: true},
toJSON: {virtuals: true}
});
Did you try Aggregation Instead of Populate. Aggregate Makes much easier for populating the embedded data using $lookup. Try the below code.
UPDATE
Topic.aggregate([{$unwind: "$thoughts"},{ $lookup: {from: 'users', localField: 'thoughts.author', foreignField: '_id', as: 'thoughts.author'}},{$sort:{{popularity : -1, date: -1}}}],function(err,topics){
console.log(topics) // `topics` is a cursor.
// Perform Other operations here.
})
Explanation:
$unwind: Deconstructs an array field from the input documents to output a document for each element.
$lookup: The $lookup stage does an equality match between a field from the input documents with a field from the documents of the “joined” collection. The lookup does the population's job.
$lookup works like
from : this says from which collection the data needs to be populated.(users in this scenario).
localField : this is the local field which needs to be populated. (thoughts.author in this scenario).
foreignField : this is the foreign field present in the collection from which data needs to be populated (_id field in users collection in this scenario).
as : this is the field as what you want to display the joined value as. (this will project thoughts.author's id as thoughts.author document).
Hope this works.

Displaying friends list using node.js and MongoDB

I am new to a node.js and I am trying to use this application https://github.com/knoldus/Node.js_UserLogin_Template
However, I cannot see friends list. I do not know what is the problem with ?
Could you help me with issue ?
Thanks
//Your user schema is looks like that
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
//unique: true,
required: true
},
password: {
type: String,
required: true
},
friends: {
type: Array // here you can put your friends collections _id like this [ObjectId("id1"), ObjectId("id2")]
}
});
const FriendSchema = new Schema({
user_from: {
type: Schema.Types.ObjectId,
ref: "users",
required: true
},
user_to: {
type: Schema.Types.ObjectId,
ref: "users",
required: true
},
is_accepted: {
type: Boolean,
default: false
},
date: {
type: Date,
default: Date.now
}
});
//and your query will look like that --
FriendModel.aggregate([
{
$lookup: {
from: "users", // users collection
localField: "_id", // friends collection id
foreignField: "friends", // friends field in you users collection document
as: "myfriends" //any alias name you can use
}
},
{
$match: { // matching condition for current user's friends from friends collection including current user
$or: [
{ user_to: mongoose.Types.ObjectId(req.user.id) }, // req.user.id referrers to logged in user object id
{ user_from: mongoose.Types.ObjectId(req.user.id) },
]
},
$match: { is_accepted: true} // that condition is for is user accepted friend request or not.
}
,
{ // filtering logged in user from the friend list
$project: {
myfriends: { // myfriends is alias name that you used in $loopup part
$filter: {
input: "$myfriends",
as: "item",
cond: { $ne: [ "$$item._id", mongoose.Types.ObjectId(req.user.id) ] }
}
}
}
}
])

Resources