I am currently working with mongoose ODM and MongoDB. Currently, I have faced a tiny issue that I can't seem to get going. So I have a User collection like so:
const userSchema = new Schema({
name: String,
posts: [{type: Schema.Types.ObjectId, ref: 'Post'}],
createdAt: Date,
updatedAt: Date
})
and a Post collection like so:
const postSchema = new Schema({
text: String,
user: {type: Schema.Types.ObjectId, ref: 'User'},
createdAt: Date,
updatedAt: Date
})
The user collection has a posts field embedded in it that is an array of the user posts. A typical example of what it looks like is given below:
{
_id: 56we389iopks,
name: John,
posts: ['6748ufhsgshsklop...', '5e43tiodo...']
}
A user has an array of posts, I find the user by their respective ID and populate the posts array.
AIM
I want to be able to fetch a user by their ID and get all posts, which are sorted with the newest and oldest post. I know mongoose has an aggregate method, but I don't know how to go about this. Thank you, any help will be appreciated.
Example of expected output document:
const user = {
_id: 5e34647489930hhff494,
name: John,
posts: [
{
text: 'aloha',
createdAt: 2021-08-30 // newest post
updatedAt: 2021-08-30
},
{
text: 'heyyy',
createdAt: 2021-02-14 // oldest post
updatedAt: 2021-02-14
},
]
}
You have the #Populate from Mongoose that can help you out.
Let's try the following
Let's retrieve posts by User
const postsByUser = await User
.find({ _id: '...' })
.populate('posts');
console.log('postsByUser', postsByUser);
Once we're able to retrieve the posts, let's improve it ordering them by the createdAt
const postsByUserOrdered = await User
.find({ _id: '...' })
.populate({
path: 'posts',
options: { sort: { 'createdAt': -1 } }
})
.exec();
console.log('postsByUserOrdered', postsByUserOrdered);
Related
This question already has answers here:
Join two collections using mongoose and get data from both
(1 answer)
Mongoose populate does not populate array
(3 answers)
How to populate array of objects in mongoose
(2 answers)
Closed 11 months ago.
I am trying to pull data (particularly, the memberships array) from a certain user.
_id:623db49b9da63a7758307d68
email:"test#test.com"
username:"test"
salt:"eb83e08efef62630c46b1809c7671db2109ceccff9d66862c93df1e3825c1354"
hash:"e419edb1bc481d5c87dd223a7f1dc20bece679fad1a3c1afe05dfabba341c0a9913a60..."
__v:0
memberships:Array
0:623dc03f72436863b72c396e
1:623dc869b61c3799494b2657
However, when I request the document, it does not retrieve the memberships portion.
{
_id: new ObjectId("623db49b9da63a7758307d68"),
email: 'test#test.com',
username: 'test',
__v: 0
}
The code below is responsible for pulling the information from the DB and then also logs what was pulled.
module.exports.showContent = async(req, res, next) => {
try {
const user = await User.findById(req.user.id);
console.log(user)
res.render('index', { user })
} catch (e) {
console.log(e)
res.render('index')
}
}
Funny enough, if there is only one ref in the array, it will show up in the console.log!
{
_id: new ObjectId("623db49b9da63a7758307d68"),
email: 'test#test.com',
username: 'test',
__v: 0,
memberships: new ObjectId("623dc869b61c3799494b2657")
}
Schema model in question:
const UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
},
username: {
type: String,
required: true,
unique: true,
},
roles: String,
memberships: {
type: Schema.Types.ObjectId,
ref: 'Community',
},
posts: {
type: Schema.Types.ObjectId,
ref: 'Posts',
},
comments: {
type: Schema.Types.ObjectId,
ref: 'Comments',
}
})
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('User', UserSchema);
My other collections do not seem to have the same issue when it comes to refs inside an array.
I have tried using .populate() to populate the data. Tried using path: and populate: within the method. Tried googling the issue, but to no luck.
I have created two model users and post where users reference post.
This is my user model.
var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/demo",{ useNewUrlParser: true });
var Post = require('./post');
var UserSchema = mongoose.Schema({
username: String,
password: String,
posts:[{
type:mongoose.Schema.Types.ObjectId,
ref: Post
}] });
This is my post model
var postSchema = mongoose.Schema({
title:String,
content: String,
likes:Number
});
When I create and save a new post to a specific user I get an id in the post field of the user model. like this:-
{
posts: [
60e33a2d18afb82f8000d8f0,
],
_id: 60d9e931b5268920245c27f0,
username: 'user1',
password: '1234',
__v: 5
}
How do I access and display the contents of the post field?
Thanks!
You can get and display Reference data in MongoDB with Aggregate.
Like:
await User.aggregate([
{
$match: { _id: ObjectId(user._id)}
},
{
$lookup: { from: "Posts", localField: "post", foreignField: "_id", as: "posts" }
},
{
$unwind: { path: "$users", preserveNullAndEmptyArrays: true,}
}, {
$project: {
" Now you can select here whatever data you want to show "
}
}
])
PS: If you have any confusion please do comment.
Thanks
Mongoose has a powerful alternative to using aggregate. You can do this with populate .
Use it like this 👇 this will populate the ids inside the posts field with the actual post documents.
User.findById(id).populate('posts')
// Full example
try {
const user = await User.findById(id).populate('posts').exec();
for (let post of user.posts) {
console.log(post.content);
}
} catch (ex) {
console.error(ex);
}
If I may, a small suggestion on how to setup your document model or just to show you an alternative. Don't reference the Posts inside your User Document. Do it the other way round and reference the User inside your Post Document.
const postSchema = mongoose.Schema({
title : String,
content : String,
likes : Number,
user : { type:mongoose.Schema.Types.ObjectId, ref: 'User' }
});
// GET ALL POSTS FROM ONE USER
Post.find({user:id});
This way you don't need to use populate when trying to show all posts of 1 user.
Having an issue with returning territories related to their Company when running query for retrieving all Companies. When a company is created, a territory is not set. Territories are created separately.
The connections/relations are "territories" within Company and "parentCompany" within Territory. When running getAllTerritories the "parentCompany" is populated correctly (I believe this is due to the fact that you choose the parentCompany upon creation of Territory.
So I guess my question is, What is the best way to populate the territories array on Company when running the getAllCompanies query?
Types:
type Company {
_id: ID
state: String!
name: String
territories: [Territory]
createdAt: String
updatedAt: String
createdBy: User!
}
type Territory {
_id: ID
name: String
parentCompany: Company!
issues: [Issue]
prodAdmins: [ProdAdmin]
masterAgents: [MasterAgent]
createdAt: String
updatedAt: String
createdBy: User!
}
Query:getAllCompanies: [Company]
getAllCompanies: async (_, args, { Company, Territory }) => {
const companies = await Company.find({})
.sort({ createdAt: 'desc' })
.populate({
path: 'territories',
model: 'Territory',
})
.populate({
path: 'createdBy',
model: 'User',
});
return companies;
},
On Company schema:
territories: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'Territory',
},
I have a very simple mongo scheme I'm accessing with mongoose
I can map the username and firstname to each notification's from field by using populate, the issue is I can't seem to get any sorting to work on the date field
With this code I get an error of
MongooseError: Cannot populate with sort on path notifications.from
because it is a subproperty of a document array
Is it possible to do this a different way, or newer way (deep populate, virtuals)? I'm on Mongoose 5.
I'd rather not use vanilla javascript to sort the object afterwards or create a separate schema
var UserSchema = new Schema({
username: String,
firstname: String,
notifications: [
{
from: { type: Schema.Types.ObjectId, ref: 'User'},
date: Date,
desc: String
}
]
});
app.get('/notifications', function(req, res) {
User.findOne({ _id: req._id }, 'notifications')
.populate({
path: 'notifications.from',
populate: {
path: 'from',
model: 'User',
options: { sort: { 'notifications.date': -1 } }
}
})
.exec(function(err, user) {
if (err) console.log(err)
})
});
That possible duplicate is almost 2 years old about Mongo. I'm asking if there are newer or different ways of doing this in Mongoose as it has changed a bit since 2016 with newer features.
From Mongoose V5.0.12 FAQ : http://mongoosejs.com/docs/faq.html#populate_sort_order
Q. I'm populating a nested property under an array like the below
code:
new Schema({
arr: [{
child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
}] });
.populate({ path: 'arr.child', options: { sort: 'name' } }) won't sort by arr.child.name?
A. See this GitHub issue. It's a known issue but one that's
exceptionally difficult to fix.
So unfortunately, for now, it's not possible,
One way to achieve this is to simply use javascript's native sort to sort the notifications after fetching.
.exec(function(err, user) {
if (err) console.log(err)
user.notifications.sort(function(a, b){
return new Date(b.date) - new Date(a.date);
});
})
It can be achievable using nesting populate like this -
eg - schema - {donationHistory: {campaignRequestId: [ref ids]}}
await user.populate({
path: 'donationHistory.campaignRequestId',
populate: [{
path: 'campaignRequestId',
model: 'CampaignRequest',
options: { sort: { 'createdAt': -1 } },
}],
...deepUserPopulation,
}).execPopulate();
A user has project IDs but I also want to store some additional project info:
const userSchema = new Schema({
...
projects: [{
_id: {
type: Schema.Types.ObjectId,
ref: 'Project',
unique: true, // needed?
},
selectedLanguage: String,
}]
});
And I want to populate with the project name so I'm doing:
const user = await User
.findById(req.user.id, 'projects')
.populate('projects._id', 'name')
.exec();
However user.projects gives me this undesirable output:
[
{
selectedLanguage: 'en',
_id: { name: 'ProjectName', _id: 5a50ccde03c2d1f5a07e0ff3 }
}
]
What I wanted was:
[
{ name: 'ProjectName', _id: 5a50ccde03c2d1f5a07e0ff3, selectedLanguage: 'en' }
]
I can transform the data but I'm hoping that Mongoose can achieve this out the box as it seems a common scenario? Thanks.
Seems like there are two options here:
1) Name the _id field something more semantic so it's:
{
selectedLanguage: 'en',
somethingSemantic: { _id: x, name: 'ProjectName' },
}
2) Flatten the data which can be done generically with modern JS:
const user = await User
.findById(req.user.id, 'projects')
.populate('projects._id', 'name')
.lean() // Important to use .lean() or you get mongoose props spread in
.exec();
const projects = user.projects.map(({ _id, ...other }) => ({
..._id,
...other,
}));
try something like this
populate({path:'projects', select:'name selectedLanguage'})