Require a mongodb db model deign. Whether to choose Refs or Embeded doc - node.js

I'm designing a backend for a Talent hunt application. Initially I designed the DB using refs. Like I followed the primary key forign key using refs to join my collections. But as the collection requirement increases it's being hard to join all the collections. Then I come across the one to many collections. So I'm thinking to get a suggestion from experts. Let me tell the requirements.
I have the following collections initially.
User.js
{
_id: "5ecfdc903165f709b49a4a14",
name: "Lijo",
email: "lijo#gmail.com"
}
then I have a category table for serving the categories
Category.js
{
_id: 5ecfdc903165f709b49a5a18,
title: "Acting",
code: "ACT"
}
If one user adds his talents to profile. I used one another collection for storing. Rmember the user can save multilple talents. So i created,
UserTalents.js
{
_id: "5ecfdc903165f709b49a6c87",
categoryId: "5ecfdc903165f709b49a5a18",
userId: "5ecfdc903165f709b49a4a14",
level: "beginner"
}
For each catgeory need to upload atleast one media along with description Soagain I created a new collection for that.
Media.js
{
_id: "5ecfdc903165f709b49a8a14",
talentId: "5ecfdc903165f709b49a6c87",
userId: "5ecfdc903165f709b49a4a14"
media: "5ecfdc903165f709b49a4a14_1.jpg"
}
And I need to have these users connected. For that craeted.
Friends.js
{
_id: "5ecfdc903165f709b49a8a18",
sender: "5ecfdc903165f709b49a4a14",
receiver: "5ecfdc903165f709b49a4a15"
status: "accepted"
}
Is this good to continue??? Expecting a huge amount of users. Or can I follow like:
User.js
{
_id: "5ecfdc903165f709b49a4a14",
name: "Lijo",
email: "lijo#gmail.com",
talents: [
{
_id: "5ecfdc903165f709b49a5a18", // _id from Category
title: "Acting",
code: "ACT",
level: "beginner",
media: "5ecfdc903165f709b49a4a14_1.jpg"
}
],
friends: [
{
_id: "5ecfdc903165f709b49a4a15",
name: "Test",
status: "approved"
}
]
}
I I follow this, then how do I update the name fileds in talents and friends array either one of its original name is changed?
Which is better approach?

Related

MongoDB/Mongoose: How can I count documents in one collection and add it to another based on id?

Hello beautiful community. Hope you guys are doing okay.
So I'm working on this app where I have two MongoDB collections, Posts and CommentsView.
Posts collection consists of post title, post type and their comments. The comments is an array of objects that consists of how many times each comment is viewed and the id.
In the CommentsView collection, I intend to store all the comments when they are viewed. Duplicates is not a problem.
Here's how the schema looks like:
Posts Schema:
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [ {
totalViews: {
type: Number
},
uid: {
type: String
}
}],
postId: {
type: String
}
} );
CommentsView Schema:
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
},
uid: {
type: String
}
} );
Suppose I have a post with two comments with uid 'A' and 'B' respectively. Whenever the comment with uid 'A' is viewed, I will create a comment in CommentsView collection. And automatically add 1 view in Posts collection's totalView field. When the same comment is viewed again, I will first add it in CommentsView collection then increment totalView field in Posts collection.
Suppose I have these documents in Comments collection:
{
text: 'Life is good',
uid: 'A'
},
{
text: 'Boom Boom',
uid: 'B'
},
{
text: 'Bam Bam',
uid: 'A'
},
So the Posts document will look like this:
{
postTile: '60 seconds to Mars',
comments: [
{
uid: 'A',
totalViews: 2,
},
{
uid: 'B',
totalViews: 1,
},
],
postId: '1s93njs'
}
I have not tried anything yet as I have no idea where to start. It seems so complicated.
How can I achieve this if I want the whole process to be automatic?
Since you are using two different schema, I recommend you to use references,
beacause if you want to add more features like number of likes and threads then this model suggested below will be useful
Don't use String type for storing Id's of the model use the type ObjectId provided by the mongoose module.
Post Model
const postsSchema = new mongoose.Schema( {
postTitle: {
type: String
},
comments: [
type:ObjectId,
ref:"model name"
],
postId: {
type: String
}
}, {timestamps:true});
Comment Model
const commentsViewSchema = new mongoose.Schema( {
text: {
type: String,
required:true
},
totalView:{
type: Number,
default:0
}
},{timestamps:true} );
now each time a post is viewed you can search for the commment id and increment the count in the document. and everything will be reflected in the post model if you populate all the comments in the array
Example
work flow
User Viewed the comment-->(getcomment_id) --> update the comment
Now all subsequent request for the post with this comment will also have the updated view because of reference.
query to update the comments
comment.findByIdAndUpdate({_id:<comment_id>}, {$inc:{totalView:1}})
other way is to have embedded do, I recommend this if you think there won't be much comments for the posts, otherwise having the above model is good.
you can further refer to these articles
rules to follow for designing mongodb schema
Hope this helped you get a good idea

MongoDB, How to get ObjectId that just created in Embedded document?

I'm creating a Post & Comments, and I designed to post contains comments. "Comments" is embedded document(Object Array) field that contains comment information, and uses "_id" as unique identifier. This is the code(Note that I'm using Node.js with extra mongoDB library) :
db.update('posts', {
_id: new ObjectID(postId)
}, {
$push: {
comments: {
_id: new ObjectID(),
author: comment.author,
email: comment.email,
text: comment.text
}
}
}) ...
Look at the $push, you can see that I created new ObjectId. It's working well, but I want to send back the _id that just created comment to the client, so make client can erase or edit without page refresh. How do I get the _id that just created embedded document?
How about setting them to variables when you create the ids:
const id1=new ObjectID(postId);
const id2=new ObjectID();
db.update('posts', { _id: id1 },
{ $push:
{ comments: { _id: id2,
author: comment.author,
email: comment.email,
text: comment.text } } }) ...

How to include documents as subdocuments in MongoDB?

I have a basic user collection that consists of document like so:
{
user: 3949fj9fn9rjhfne93,
name: "Jerry",
country: 'au',
friends: ['20fn39r4hf93', 'g9en3irhr934', '4i5henifuw92']
}
Each of those friends also has a document the same, along with there being a large collection of countries that can be queried via the country code.
{
code: 'AU',
name: 'Australia'
}, {
code: 'NZ',
name: 'New Zealand'
}
My question is, how would I include the full document for each of those array items within the result, like so:
{
user: 3949fj9fn9rjhfne93,
name: "Jerry",
country: {
code: 'AU',
name: 'Australia'
},
friends: [{
user: 20fn39r4hf93,
name: "Bob",
friends: ['2049429fr', 'djwij393i4']
}, {
user: g9en3irhr934,
name: "Foo",
friends: []
}, {
user: 4i5henifuw92,
name: "Bar",
friends: ['2049429fr']
}]
}
I am using Mongoose in my application, and I understand that this could be done by using a simple for loop and pushing the results to the user object and then returning it in node with res.json(user), although what if the user had hundreds (or even thousands) of friends? The request would be huge. I also need to do this in multiple places within my API.
Is there a more efficient way to achieve this?

Conditional update, depending on field matched

Say I have a collection of documents, each one managing a discussion between a teacher and a student:
{
_id,
teacherId,
studentId,
teacherLastMessage,
studentLastMessage
}
I will get queries with 3 parameters: an _id, a userId and a message.
I'm looking for a way to update the teacherLastMessage field or studentLastMessage field depending on which one the user is.
At the moment, I have this:
return Promise.all([
// if user is teacher, set teacherLastMessage
db.collection('discussions').findOneAndUpdate({
teacherId: userId,
_id
}, {
$set: {
teacherLastMessage: message
}
}, {
returnOriginal: false
}),
// if user is student, set studentLastMessage
db.collection('discussions').findOneAndUpdate({
studentId: userId,
_id
}, {
$set: {
studentLastMessage: message
}
}, {
returnOriginal: false
})
]).then((results) => {
results = results.filter((result) => result.value);
if (!results.length) {
throw new Error('No matching document');
}
return results[0].value;
});
Is there a way to tell mongo to make a conditional update, based on the field matched? Something like this:
db.collection('discussions').findOneAndUpdate({
$or: [{
teacherId: userId
}, {
studentId: userId
}],
_id
}, {
$set: {
// if field matched was studentId, set studentLastMessage
// if field matched was teacherId, set teacherLastMessage
}
});
Surely it must be possible with mongo 3.2?
What you want would require referencing other fields inside of $set. This is currently impossible. Refer to this ticket as an example.
First of all, your current approach with two update queries looks just fine to me. You can continue using that, just make sure that you have the right indexes in place. Namely, to get the best performance for these updates, you should have two compound indexes:
{ _id: 1, teacherId: 1 }
{ _id: 1, studentId: 1 }.
To look at this from another perspective, you should probably restructure your data. For example:
{
_id: '...',
users: [
{
userId: '...',
userType: 'student',
lastMessage: 'lorem ipsum'
},
{
userId: '...',
userType: 'teacher',
lastMessage: 'dolor sit amet'
}
]
}
This would allow you to perform your update with a single query.
Your data structure is a bit weird, unless you have a specific business case which requires the data the be molded that way i would suggest creating a usertype unless a user can both be a teacher and a student then keep your structure.
The $set{} param can take a object, my suggestion is to do your business logic prior. You should already know prior to your update if the update is going to be for a teacher or student - some sort of variable should be set / authentication level to distinguish teachers from students. Perhaps on a successful login in the callback you could set a cookie/local storage. Regardless - if you have the current type of user, then you could build your object earlier, so make an object literal with the properties you need based on the user type.
So
if(student)
{
var updateObj = { studentLastMsg: msg }
}
else
{
var updateObj = { teacherLastMsg: msg }
}
Then pass in your update for the $set{updateObj} I'll make this a snippet - on mobile

Query/sort reference of reference in mongoose

Hopefully I can explain this well.
I have 3 Model types in play here: Users, Products, and Stores. What I'm after is a sorted list of Stores, per user, based on how many Products they've added from that Store. So basically "show me this User's top Stores".
pseudo-schemas:
var User = {
name: String
};
var Store = {
name: String
};
var Product = {
title: String,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}
store: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Store'
}
};
So how can I find which Stores the User has added the most Products to? This may be obvious, it's late. :-P
Thanks!
You can try to use Aggregation framework to solve it.
And especially $group pipeline:
// aggregate whole `Product` collection
Product.aggregate([
// count products by `user` and `store` and save result to `products_count`
{$group: {
_id: {user_id:"$user", store_id:"$store"},
products_count: {$sum: 1}
}},
// sort by most products count
{$sort: {products_count: -1}}
])
There are also $limit and $skip pipelines, that help to paginate all this stuff.

Resources