I have already looked up some questions related to this but with no luck at all. I have the following schemas
const Card= new mongoose.Schema(
{
name: { type: String },
collectionId: { type: mongoose.Schema.Types.ObjectId, ref: "Collection" },
price: { type: Number}
},
{ timestamps: true }
);
const Collection= new mongoose.Schema(
{
name: { type: String },
},
{ timestamps: true }
);
const Transactions = new mongoose.Schema(
{
amount: { type: Number},
cardId: { type: mongoose.Schema.Types.ObjectId, ref: "Card" },
collectionId: {type: mongoose.Schema.Types.ObjectId, ref: "Collection"},
userId: {type: mongoose.Schema.Types.ObjectId, ref: "User" }
},
{ timestamps: true }
);
lets say I want to aggregate transactions to get the user who paid most to buy cards and his info then I will do something like this
const T= require("transaction model")
const User = require("user model")
const res = await T.aggregate([
{
$group: {
_id: "$userId",
totalPaid: { $sum: "$amount" },
cardsBought: { $sum: 1 },
},
},
{
$lookup: {
from: "users" / User.document.name
localField: "userId",
foreignField: "_id",
as: "userInfo",
},
},
])
I followed similar questions answers and made sure that the collection name is correct in the "from" field (I tried both users and User.document.name) and made sure the localField and foreignField types are the same(they are both mongo ObjectId) but still I get userInfo as empty array and I am not really sure why.
A work around that I have tried is making two queries like the following
const res = await T.aggregate([])
await User.populate(res, {path: "_id", select: {...}})
but the issue is that I can't do multiple lookups (lets say I want to populate some other data as well)
Related
I have 2 Collections one for users and other for posts(Posts colllection have _id of users as postedBy).
In users collection each user is having friends array which have _id of users in it.I want to get all the Posts of My friends and mine post in sorted order(sorted By CreatedAt).
This is my Userschema in which i am having friends array of mongoose object type ref to user collection,
here i'm storing users id who is friend.
`//UserSchema
const userSchema = new Schema({
profileImg : {
type: String,
},
name: {
type: String,
required: [true, 'Please Enter Your Name!']
},
about: {
type: String,
},
email: {
type: String,
required: [true, 'Please Enter Email!'],
unique: [true, 'Already Registered!'],
match: [/\S+#\S+\.\S+/, 'is invalid!']
},
password: {
type: String,
required: [true, 'Please Enter Your Password!'],
},
friends: [{
type: mongoose.Types.ObjectId,
ref: 'USER'
}],
address: {
line1: {
type: String,
required: [true, 'Please Enter Your Address!']
},
line2: {
type: String
},
city: {
type: String,
required: [true, 'Please Enter Your City!']
},
state: {
type: String,
required: [true, 'Please Enter Your State!']
},
}
}, { timestamps: true })
This is my Post Schema where userId is ref to users collection and here the _id of user who is uploading post is saved.
//POST SCHEMA
const postSchema = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "USER",
required: true
},
postImage: {
type: String,
required: [true, 'Please Upload the Image!']
},
caption: {
type: String
},
likes: [likeSchema],
comments: [commentSchema]
}, { timestamps: true })
`
What I am Doing:
1st I am finding the user through _id
2nd from found user's friend array ,lookup in posts collection to get post of friends
3rd Now to get owns post again look up in post collection with own _id
4th concat the both array obtain from friend post and user post as Posts
Now here after step 4 i want to sort the Posts by createdAt but its not working..
How to sort it?
const posts = await User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(req.user_id)
}
},
{
$lookup: {
from: "posts",
localField: "friends",
foreignField: "userId",
as: "friendposts"
}
},
{
$lookup: {
from: "posts",
localField: "_id",
foreignField: "userId",
as: "userposts"
}
},
{
$project: {
"Posts": {
$concatArrays: ["$friendposts", "$userposts"]
},
_id: 0
}
}
])
you can use 1 lookup instead of 2 .
for sorting you have 3 ways
sort in the code level (using sort function)
use $unwind $sort and group (if mongo db version is less than 5.2)
use $sortArray (applicable for mongodb version 5.2+)
if using 2nd method.
User.aggregate([
{
'$match': {
'_id': mongoose.Types.ObjectId(req.user_id)
}
}, {
'$addFields': {
'users': {
'$concatArrays': [
'$friends', [
mongoose.Types.ObjectId(req.user_id)
]
]
}
}
}, {
'$lookup': {
'from': 'posts',
'localField': 'users',
'foreignField': 'userId',
'as': 'posts'
}
}, {
'$unwind': {
'path': '$posts'
}
}, {
'$sort': {
'posts.createdAt': -1
}
}, {
'$group': {
'_id': '$_id',
'posts': {
'$push': '$posts'
},
'name': {
'$first': '$name'
}
}
}
])
you can add any other field needed in final response like wise i added name .
I am getting some data in an array of object like this :
{
"success": true,
"result": {
"docs": [
{
"_id": "60a602901a74f62935a4898f",
"user": "607030ba3c82e235443db610",
"weekNum": 19,
"__v": 0,
"createdAt": "2021-05-20T06:32:48.742Z",
"data": [
{
"activity": "6063f898232d3f2acca5d2ae",
"_id": "60a6063668f27715b0f08753",
"project": "60702d1f3c82e235443db5ff",
"task": "60702d3d3c82e235443db601",
"workingDate": "2021-05-10T18:30:00.000Z",
"dayVal": 1,
"description": ""
}
],
"managersComment": "leleleleelelel",
"status": "Submitted",
"updatedAt": "2021-05-20T06:48:22.163Z"
}
],
"paginator": {
"itemCount": 1,
"offset": 0,
"perPage": 10000,
"pageCount": 1,
"currentPage": 1,
"slNo": 1,
"hasPrevPage": false,
"hasNextPage": false,
"prev": null,
"next": null
}
}
}
my schema for this collection in like this:
const timesheetSchema = new Schema({
managersComment: {
type: String
},
weekNum: {
type: Number
},
data:[{
project: {
type: Schema.ObjectId,
ref: projectModel
},
task: {
type: Schema.ObjectId,
ref: taskModel
},
activity: {
type: Schema.ObjectId,
default: null,
ref: activityModel
},
workingDate: {
type: Date
},
dayVal: {
type: Number
},
description: {
type: String
},
}],
user: { type: ObjectId, ref: userModel },
status: {
type: String,
enum: ['Saved', 'Submitted', 'Approved', 'Rejected', 'Reset']
},
}, { timestamps: true });
timesheetSchema.plugin(mongoosePaginate);
const timesheetModel = mongoose.model('timesheet', timesheetSchema);
my code for getting data is something like this:
try {
console.log('populateRequired --------------------------------------------------')
const populateArray = [
{ path: "task", select: "taskName" },
{ path: "project", select: "projectName" },
{ path: "activity", select: "title" },
];
const query = {
user: req.params.userId,
status: req.query.status,
};
const paginationParams = {
populate: populateArray,
customLabels: customLabels,
limit: req.query.limit,
};
console.log("USER QUERY ", query);
const userTimesheet = await getTimesheetDataByUserId(
query,
paginationParams
);
console.log(userTimesheet);
res.send({ success: true, result: userTimesheet });
} catch (err) {
console.log(err);
next(err);
}
But as shown in return data above i am not getting populate applied in data array. Please help not sure what to do.
According to the data you posted, I think that the issue is that you're not creating virtual fields to populate with your references. Your fields project, task and activity in each array element, or user, are meant to be ids referring to the corresponding models. But those ids alone will not implement the population, they are only the pointers that the population will need in order to be executed. To make that a little bit more clear, I would change those names to userId: { type: ObjectId, ref: userModel }.
After that, you will need to create the virtual fields:
timesheetSchema.virtual("user", {
ref: "userModel",
localField: "userId",
foreignField: "_id",
justOne: true,
});
Finally, if you want to have the virtual field timesheet.user populated each time you query your collection, you will have to add some middleware to your schema. For me, the most reasonable way to make this work is:
timesheetSchema.pre("find", function (next) {
this.populate("user");
next();
});
Just to have a complete solution: I think this will solve your problem for the timesheet.user field. But I don't think it will work in your data array. In fact, I'm not 100% sure the way you're defining it is really going to work: creating a timesheet with an array of imputations doesn't make too much sense to me. A more coherent approach would be creating a collection of all the imputations that looked like this:
const dataSchema = new Schema({
projectId: {
type: Schema.ObjectId,
ref: projectModel
},
taskId: {
type: Schema.ObjectId,
ref: taskModel
},
activityId: {
type: Schema.ObjectId,
default: null,
ref: activityModel
},
userId: {
type: ObjectId,
ref: userModel
},
workingDate: {
type: Date
},
dayVal: {
type: Number
},
description: {
type: String
},
});
With virtual fields like:
dataSchema.virtual("project", {
ref: projectModel,
localField: "projectId",
foreignField: "_id",
justOne: true
});
And so on. I would populate each field just like I showed you with the user example. Then, for the timesheet schema I would only reference userId, and populate data like this:
const timesheetSchema = new Schema({
managersComment: {
type: String
},
weekNum: {
type: Number
},
userId: {
type: ObjectId,
ref: dataModel
},
status: {
type: String,
enum: ['Saved', 'Submitted', 'Approved', 'Rejected', 'Reset']
},
}, { timestamps: true });
timesheetSchema.virtual("data", {
ref: dataModel,
localField: "userId",
foreignField: "userId"
});
timesheetSchema.virtual("user", {
ref: userModel,
localField: "userId",
foreignField: "_id",
justOne: true
});
This way you would have a collection with all the imputations for all the users, and you would be able to query and filter that collection for each userId, projectId or anything you would need. Having an array inside your timesheet collection would make this quite more difficult.
One simple solution I found on another SO post is like this:
const result = await timesheetModel.findOne(query).populate({
path: 'data.project data.activity data.task'
});
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.
I am currently learning aggregation and some related methods, after following mongoose and mongodb docs i tried it. but i am having problem.
Follow.aggregate([
{ $match: { user: mongoose.Types.ObjectId(userId) } },
{ $unwind: '$followers' },
{
$lookup: {
from: 'accounts',
localField: 'followers',
foreignField: '_id',
as: 'followers'
}
},
{ $project: { name: 1, photo: 1 } },
]).exec((err, followers) => {
if (err) throw err;
console.log(followers);
res.send(followers);
});
I want to get the followers of that userID and select the followers names and photo, but i am only getting the objectid of the matched document
[ { _id: 5bfe2c529419a560fb3e92eb } ]
expected output
[ { _id: 5bfe2c529419a560fb3e92eb , name: 'john doe", photo: 'cat.jpg'} ]
Follow Model
const FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
},
following: [{
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
}],
followers: [{
type: Schema.Types.ObjectId,
ref: 'Account',
required: true,
}],
});
When the $project stage is wrong you would get that result. Try removing it first and see the output. To me it seems you should have something like this there:
{ $project: { "followers.name": 1, "followers.photo": 1 } },
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) ] }
}
}
}
}
])