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);
I'm working on a chat app that allows group app. In order for me to achieve group chat feature, I added a table called ChatRoomUser that connects ChatRoom and Message tables. I got everything working but can't figure out to query Chat Room in a chronological order.
query getUser(id: "USER_ID"){
chatRoomUser(sortDirection: DESC) { // Can't query this in chronological order...
items {
id
chatRoom{
id
receiverHasRead
createdBy
}
}
}
}
I was going to update updatedAt under ChatRoomUser when there's a new message in a chat room but that won't be efficient when there's many users in a room. Because I will have to update 100 rows if there's 100 users in a chat room.
What will be the best way to solve this issue?
type User
#model
#auth(rules: [
{ allow: private }
]
) {
id: String!
chatRoomUser: [ChatRoomUser] #connection(name: "UserChatRoomUser", sortField: "updatedAt") # this does not seem like a good way to query
}
type ChatRoomUser
#model
#key(name: "gsi-doesChatRoomExist", fields: ["chatRoomUserUserId", "members"], queryField: "doesChatRoomExist")
#auth(rules: [
{ allow: private }
]
) {
id: ID!
chatRoomUserUserId: String!
chatRoomUserChatRoomId: String!
members: String!
user: User #connection(name: "UserChatRoomUser")
chatRoom: ChatRoom #connection(name: "ChatRoomUserChatRoom")
createdAt: AWSDateTime
updatedAt: AWSDateTime # I was going to use this as sortField
}
type ChatRoom
#model
#auth(rules: [
{ allow: private }
]
) {
id: ID!
createdBy: String!
receiverHasRead: Boolean!
chatRoomUsers: [ChatRoomUser] #connection(name: "ChatRoomUserChatRoom")
messages: [Message] #connection(name: "chatByChatRoom", sortField: "createdAt")
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
type Message
#model
#auth(rules: [
{ allow: private }
]
) {
id: ID!
content: String!
messageChatRoomId: String!
messageUserId: String!
user: User #connection(name: "UserMassages")
chatRoom: ChatRoom #connection(name: "chatByChatRoom")
createdAt: AWSDateTime!
updatedAt: AWSDateTime
}
I have a feeling that I need to change schema design to make this work but can't think of any solution that can work.
You'll want to implement a key that lets you query your Chatroom for Messages by createdAt. Check out this page on defining keys for more information.
Doing this 👇
query {
postsConnection(where: {
status: PUBLISHED
}) {
aggregate {
count
}
edges {
cursor
node {
id
slug
}
}
}
}
gives me postsConnection of published posts.
The Post model has an array of Category enum in field categories. This is the Post in datamodel 👇
enum Category {
TECH
FIN
DIGIMARK
CODING
TUTORIAL
HOWTO
WRITING
INSPIRE
SCIENCE
POLITICS
LIFESTYLE
}
type Post {
id: ID!
title: String!
editorSerializedOutput: Json!
editorCurrentContent: Json!
editorHtml: String!
updatedAt: DateTime!
createdAt: DateTime!
author: User
authorId: String!
categories: [Category!]!
thumbnail: Json!
status: PostStatus!
slug: String!
}
My question is, what Prisma Query do I need to write to get PostConnection of posts in a specific category?
Prisma doesn't yet allow filtering with Enum (see issue on github)
You can however make a to-many relation with a new Type Category that you can create
I have a post route that receives data from a PUT request in an express app that aims to update a mongoose document based on submitted form input. The "Base" model is Profile, and I have two discriminator models Helper and Finder that conditionally add fields to the Profile schema (see below for details).
Thus, req.body.profile will contain different fields depending on the discriminator it's associated with, but will always contain the fields (username, email city, accountType) present in the "base" model, Profile.
Before I send my PUT request, an example of a document in Profile looks like this:
{ jobTitle: '',
lastPosition: '',
email: '',
city: '',
accountType: 'helper',
_id: 5c77883d8db04c921db5f635,
username: 'here2help',
__v: 0 }
This looks good to me, and suggests that the model is being created as I want (with base fields from Profile, and those associated with the Helper model - see below for models).
My POST route then looks like this:
router.put("/profile/:id", middleware.checkProfileOwnership, function(req, res){
console.log(req.body.profile);
Profile.findOneAndUpdate(req.params.id, req.body.profile, function(err, updatedProfile){
if(err){
console.log(err.message);
res.redirect("/profile");
} else {
console.log(updatedProfile);
res.redirect("/profile/" + req.params.id);
}
});
});
The information I receive from the form (console.log(req.body.profile)) is what I expect to see:
{ accountType: 'helper',
username: 'here2help',
email: 'helpingU#me.com',
city: 'New York',
jobTitle: 'CEO',
lastPosition: 'sales rep'}
However, after updating the document with req.body.profile in Profile.findOneAndUpdate(), I do not see my returned document updated:
console.log(updatedProfile)
{ jobTitle: '',
lastPosition: '',
email: 'helpingu#me.com',
city: 'New York',
accountType: 'helper',
_id: 5c77883d8db04c921db5f635,
username: 'here2help',
__v: 0 }
So, the fields that are defined in my 'Base' model (ie those defined in ProfileSchema - see below) are being updated (e.g. city), but those that are in my discriminators are not - see below.
The updated information is clearly present in req, but is not propagated to the Profile model - How can this be?
I've also tried using findByIdAndUpdate but I get the same result.
Here are the Schemas I'm defining:
Profile - my "base" schema:
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
username: String,
complete: { type: Boolean, default: false },
email: { type: String, default: "" },
city: { type: String, default: "" }
}, { discriminatorKey: 'accountType' });
profileSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("Profile", profileSchema);
Finder
var Profile = require('./profile');
var Finder = Profile.discriminator('finder', new mongoose.Schema({
position: { type: String, default: "" },
skills: Array
}));
module.exports = mongoose.model("Finder");
Helper
var Profile = require('./profile');
var Helper = Profile.discriminator('helper', new mongoose.Schema({
jobTitle: { type: String, default: "" },
lastPosition: { type: String, default: "" }
}));
module.exports = mongoose.model("Helper");
This is my first attempt at using discriminators in mongoose, so it's more than possible that I am setting them up incorrectly, and that this is the root of the problem.
Please let me know if this is unclear, or I need to add more information.
It matters what schema you use to query database
Discriminators build the mongo queries based on the object you use. For instance, If you enable debugging on mongo using mongoose.set('debug', true) and run Profile.findOneAndUpdate() you should see something like:
Mongoose: profiles.findAndModify({
_id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
'$set': {
email: 'finder#me.com',
city: 'New York',
accountType: 'helper',
username: 'User NAme', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })
Notice it uses only the fields defined in Profile schema.
If you use Helper, you would get something like:
profiles.findAndModify({
accountType: 'helper',
_id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
'$set': {
jobTitle: 'CTO',
email: 'finder#me.com',
city: 'New York',
accountType: 'helper ',
username: 'User Name', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })
Notice it adds the discriminator field in the filter criteria, this is documented:
Discriminator models are special; they attach the discriminator key to queries. In other words, find(), count(), aggregate(), etc. are smart enough to account for discriminators.
So what you need to do when updating is to use the discriminator field in order to know which Schema to use when calling update statement:
app.put("/profile/:id", function(req, res){
console.log(req.body);
if(ObjectId.isValid(req.params.id)) {
switch(req.body.accountType) {
case 'helper':
schema = Helper;
break;
case 'finder':
schema = Finder;
break;
default:
schema = Profile;
}
schema.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
if(err){
console.log(err);
res.json(err);
} else {
console.log(updatedProfile);
res.json(updatedProfile);
}
});
} else {
res.json({ error: "Invalid ObjectId"});
} });
Notice, above is not necessary when creating a new document, in that scenario mongoose is able to determine which discriminator to use.
You cannot update discriminator field
Above behavior has a side effect, you cannot update the discriminator field because it will not find the record. In this scenario, you would need to access the collection directly and update the document, as well as define what would happen with the fields that belong to the other discriminator.
db.profile.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
if(err) {
res.json(err);
} else {
console.log(updatedProfile);
res.json(updatedProfile);
}
});
Please add option in findOneAndUpdate - { new: true };
In Moongose findOneAndUpdate() Method have four parameters
like
A.findOneAndUpdate(conditions, update, options, callback) // executes
And you need to execute like this
var query = { name: 'borne' };
Model.findOneAndUpdate(query, { name: 'jason bourne' }, options, callback)
or even
// is sent as
Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options, callback)
This helps prevent accidentally overwriting your document with { name: 'jason bourne' }.
I have a User schema created with Mongoose that holds a customer array, and inside that customer array there is another fleet array. I have figured out how to push to the customer array within the User schema, however I can not figure out how to loop through the customers, find the customer via id or name, and then push data into the fleet array.
Here is my User schema:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: {
type: String,
default: ''
},
password: {
type: String,
default: '',
},
registerDate: {
type: Date,
default: Date.now()
},
customer: [{
name: {
type: String,
default: '',
},
email: {
type: String,
default: 'No email addresses found',
},
fleet: [{
unitType: {
type: String,
default: 'No type selected',
},
unitNumber: {
type: String,
default: 'No unit number provided',
},
vinNumber: {
type: String,
default: 'No vin number provided'
},
}]
}]
});
module.exports = mongoose.model('User', UserSchema);
I was able to figure out how to push a new customer into the customer array no problem. My problem is I do not get how I can loop through the customer array, and once I find the customer that I need, push data into the fleet array. I am really just stumped on how to word my Google search! Hopefully this is detailed enough, and please let me know if you need to see any other code.
You need to use positional '$' operation. Read here
UserSchema.update(
{'customer.email':'xyz#email.com'},
{
$push:{
'customer.$.fleet':<fleet object>
}
},function(err,result){
})
--- OR ---
UserSchema.update(
{'customer.email':'xyz#email.com'},
{
$set:{
'customer.$.fleet':[fleet array]
}
},function(err,result){
})
Hope this helps.