How to implement many to many relationship in mongoDb? - javascript

I am working on a photography blog, in which I need to store images according to the tags provided by the user. I tried googling and came up with implementing a many to many relationship in mongoDb , but I am still confused as how to take multiple inputs from the user and store them separately . I tried something like this but I don't know how to proceed further.
This is my picture model:-
const mongoose = require("mongoose");
const snapSchema = new mongoose.Schema({
Caption: {
type: String,
required: "Caption cannot be blank."
},
image: String,
imageId: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
tags: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tag"
}
]
});
module.exports = mongoose.model("Snap", snapSchema);
This is my tag model:-
const mongoose = require("mongoose");
const tagSchema = new mongoose.Schema({
text: String,
snaps: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Snap"
}
]
});
module.exports = mongoose.model("Tag", tagSchema);

If you think about it, usually an Image can have multiple Tags, not the other way around. I wouldn't associate Images with Tags directly. If you want to present images tag-wise, you simply filter your images by tags associated with them. So, your Snap model is perfect, but your Tag does not need association with any Image (it can live without an image, right?).
For presentation purposes, you do a simple filter:
Order.find( { tags: your_tag_id } )
Here's a great example of how to query by array content: https://docs.mongodb.com/manual/tutorial/query-arrays/
As for your update scenario, I imagine that user can input multiple tags for a single image, right? In that case, you simply insert relevant tag ids to tags image field.

Related

Mongoose, $pull an element from nested array and update document based on the presence of the element

I am working on a mongoose schema similar to this:
const actionSchema = {
actions: {
type: [{
actionName: {
type: String,
required: true
},
count: {
type: Number,
default: 0,
required: true
},
users: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}]
}};
It is a nested schema of a post schema.
Here, actions are dynamically generated and number of people does that action are maintained by count and their identity is maintained by users array.
As you see, actions is an array of objects which further contain users array.
I want to check if a provided user id is present in any of the action object and then remove it from array and also reduce the count.
Being totally new to mongoose and mongodb, one simple way I see is to find the post using Post.findById() which has to be updated, run js loops, update the post and call .save(). But it can be very costly when users array has thousands of user ids.
I tried .update() but can't understand how to use it in this case.
How about adding a method to the Post Model (like postSchema.methods.removeUserAction)? This gives access to document from this and allows to update the document and thus call .save(). Does it loads the full document to the client node application?
So please suggest the right way.
Thank you.
You should simplify your model, for example
// Model - Actions Model
const actionSchema = {
actionName: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
};
And you can easily get the total actions via Model.count(), get specific action count with Model.count({ actionName: 'action name'}), and removing entries with Model.delete(condition). Unless there's a reason why you have it modeled this way.

Mongoose: Populate path using field other than _id

By default mongoose/mongo will populate a path using the _id field, and by it seems like there is no way to change the _id to something else.
Here are my two models which are connected with one-to-many relationship:
const playlistSchema = new mongoose.Schema({
externalId: String,
title: String,
videos: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Video',
}],
});
const videoSchema = new mongoose.Schema({
externalId: String,
title: String,
});
Normally, when querying a playlist you would populate videos just with .populate('videos'), but in my case I would like to use the externalId field instead of the default _id. Is that possible?
As far as I know, the way to achieve this with mongoose presently is by using virtuals. When populating virtuals, you can specify the localField and foreignField to whatever you want, so you are no longer bound to the default _id as foreignField. More details about this here.
For the scenario described in your question, you would need to add a virtual to the playerlistSchema, something like this:
playlistSchema.virtual('videoList', {
ref: 'Video', // The model to use
localField: 'videos', // The field in playerListSchema
foreignField: 'externalId', // The field on videoSchema. This can be whatever you want.
});
Now, whenever you query for player lists, you can populate the videoList virtual to get the referenced video documents.
PlaylistModel
.findOne({
// ... whatever your find query needs to be
})
.populate('videoList')
.exec(function (error, playList) {
/* if a playList document is returned */
playList.videoList; // The would be the populated array of videos
})

Users schema with poking other users schema in node.js

I am making an application in which a user can poke other users. Here is the code for the schema designs I have considered. The first is using only a users schema:
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
Another way is using a pokes schema. Here I'm storing the object ids of the pokes schema in the users schema.
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
In the third way I totally remove the relation between the two schemas:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
In the second and third ways I can query for pokes easily.
I want to know which of the three is the best design and why. Also if userA pokes userB then it can be the case that userB can also poke back to userA. I'm learning node.js currently and am confused about the design in mongoDb.
Alright, so here's the best I can do. You briefly answered my question in my comment above but I'd like to point out it's important to think about what you are doing (or expect to be doing) more and how much more. That aside though, let's take a look at each schema.
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
When we look at this first one it seems inadequate for your needs. It's a collection of users who have a name and an array of pokes they have made. If we need to know who a user has poked then that's a really easy and fast query - it's right there under their name, search by name or _id and we're done! But what happens when you want to look up who has poked this user? You will need to query every single user and then search through every single pokes array for the original user. If we have m-many users and each has n-many pokes, that's doing m* n tests. Yikes. If m and n get big that's going to be a lot (think of the difference of 100 * 100 vs 10,000 * 10,000 or even more!). Even if you personally are not coding that search in your node then mongo is doing that search. So unless you're sure that looking up who has poked a user is going to be something that is pretty rare this is probably not a good option. Moving on:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
Now we have a pokes schema, nice! If we wanted to do the search we discussed above we can instead query pokes directly based on to_user_id, and then if we need a name of all the users who initiated the pokes we can just query the users based on its _id. Not bad! We also still have the fast way to get the reverse, aka search for pokes a user has initiated, because there is still pokes in our user schema. What happens when a poke occurs, though? We have to update both schemas. So not only will we do a (relatively easy) insert into pokes, we will have to also update our pokes array of the user who did the poking. This might not be so bad, but what happens if one update fails? Now our data is inconsistent - users and pokes don't match. We're also doubling our updates every poke. This might not be a big deal, and if we're getting a user's pokes much more than we're poking then it might be an ok trade-off, but it becomes a little riskier because we've introduced somewhere we can be inconsistent. Alright, last one:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
First, note that these schemas are still related - the pokes schema reference users. It's just not doubly-related like the last one. Anyway, now we've removed the pokes array from the user schema. Ok! We don't run the risk of having inconsistent data anymore, noice! We're also not doing two updates for every poke, toit! The trade-off is now when we want to get the list of users a user has poked we have to do a similar query to the one we did above when we wanted to get a list of users a user has been poked by. Which isn't so bad, but is certainly not as fast as having the pokes array already sitting there and waiting.
In my opinion, unless you're searching for who users have poked (and not been poked by) significantly more often than doing anything else this third scenario is best. The schemas make logical sense, you're not updating twice. It's what I would use. But as I said, it's very important to consider your particular need and design.
Hope that helps!

MongoDB best practise - One to many relation

According to this post I should embed a "reference": MongoDB relationships: embed or reference?
User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdEvents: ['Event']
});
module.exports = mongoose.model('User', userSchema);
Event.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
date: {
type: Date,
required: true
}
});
module.exports = mongoose.model('Event', eventSchema);
So an embedded event looks like this in the database:
My Code works but im curious if this is the right way to embed the event. Because every example for one to many relations is made with references and not embedded.
From my experience, using embedding or referencing depends on how much data we are dealing with.
When deciding on which approach to pick, You should always consider:
1- One-To-Few: if you'll have a small number of events added to a user over time, I recommend you to pick the embedding approach as it is simpler to deal with.
2- One-To-Many: if new events are frequently added, you totally should go for referencing to avoid future performance issues.
Why?
When you are frequently adding new events, if they are being embedded inside an user document, that document will grow larger and larger over time. In the future you will probably face issues like I/O overhead. You can catch a glimpse of the evils of large arrays in the article Why shouldn't I embed large arrays in my documents?. Although it's hard to find it written anywhere, large arrays in MongoDB are considered a performance anti-pattern.
If you decide to go for referencing, I suggest reading Building with Patterns: The Bucket Pattern. The article can give you an idea on how to design your user_events collection in a non-relational way.

Saving document and its object id for referencing in other documents

In a Node.js App with Mongodb/Mongoose. i have two collections. Users and Books. My question is: when a user saves a book i have to save it in the Books collection and its object id inside Users collection for referencing. Two save operation for one user input.
Is this correct way? is this Mongodb standard? in a relation based database system its a wrong architecture but in the lack of Join in Mongodb what should i do? if i have a large database should i save current books id inside each of related collections?
I know that i can has books embedded into each user document but it has own problems.
Im confused. what should i do?
You can create a MySQL "join" in MongoDB with Mongoose. It is not the MongoDB standard but it makes developing in MongoDB sometimes a lot easier. Below are two simple example schema's.
var BookSchema = new Schema({
title: {
type String
},
author: {
type: String
}
});
module.exports = mongoose.model('Book', BookSchema);
var UserSchema = new Schema({
username: {
type: String,
required: true
},
books: [{
type: String,
ref: 'Book'
}]
});
module.exports = mongoose.model('User', UserSchema);
In the last schema there is a "books" property with a reference to books. You can store Strings of Object id's inside the array (not as an object). If you want, you can also store Object id's instead of Strings. Below is an example schema for a user:
{
username: "Fcoder",
books: ["550adf3899fbe92a168d3051", "550adf3899fbe92a168d3052"]
}
When querying your MongoDB database, you can populate the data. Your query will look like this:
User.find({}).populate('books').exec(function(err, data) {
// callback
});
Inside data, you will find something like this:
{
username: "Fcoder",
books: [{
_id: ObjectId("550adf3899fbe92a168d3051"),
title 'Some title 1',
author: 'Some author 1'
}, {
_id: ObjectId("550adf3899fbe92a168d3052"),
title 'Some title 2',
author: 'Some author 2'
}
}

Categories

Resources