Mongoose Many to many relations - node.js

I'm new to mongoDB and Mongoose, and I have some problems with relations.
My schema has 3 tables (User / Person / Family), you can see it below.
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var userSchema = Schema({
_id : Schema.Types.ObjectId,
email : String,
person : [{ type: Schema.Types.ObjectId, ref: 'Person' }] // A user is linked to 1 person
});
var personSchema = Schema({
_id : Schema.Types.ObjectId,
name : String,
user : [{ type: Schema.Types.ObjectId, ref: 'User' }] // A person is linked to 1 user
families : [{ type: Schema.Types.ObjectId, ref: 'Family' }] // A person have 1,n families
});
var familySchema = Schema({
_id : Schema.Types.ObjectId,
name : String,
persons : [{ type: Schema.Types.ObjectId, ref: 'Person' }] // A family have 0,n persons
});
var User = mongoose.model('User', userSchema);
var Person = mongoose.model('Person', personSchema);
var Family = mongoose.model('Family', familySchema);
I don't know if my schema is good, does the parameter person is require in my userSchema ? Because the informations will be duplicated, in userSchema I will have the personID and in the personSchema this wil be the userID.
If I understand it's usefull to have this duplicated values for my requests ? But if the informations is duplicated I need to execute two queries to update the two tables ?
For exemple, if I have a person with a family (families parameter in personSchema), and in the family I have this person (persons parameter in familySchema). What will be the requests to remove / update the lines in the tables ?
Thanks

IMHO, your schema seems fine if it meets your needs !! (Although, if you think your current schema fulfills your purpose without being bloated, then yeah its fine)..
"Person" seems to be the only type of a user and the entity to be connected to rest of the other entities . As long as this is the case, you can feel free to remove the person parameter from the userschema as you can access the user information from the person. But lets assume if there exists another entity "Aliens" who also has their own unique family, then it would be better to add the alien and person parameter in the "User" Schema to see the types of users.(As long as there's only one type i.e. Person, then you may not need to add it in userschema). In case, if you still like to keep it, then please make the following change too in your schema as you are passing the array although it seems to be one to one relation !
var userSchema = Schema({
_id : Schema.Types.ObjectId,
email : String,
person : { type: Schema.Types.ObjectId, ref: 'Person' } // A user is linked to 1
//person // Here I have removed the []
});
var personSchema = Schema({
_id : Schema.Types.ObjectId,
name : String,
user : { type: Schema.Types.ObjectId, ref: 'User' } // removed [] here too
families : [{ type: Schema.Types.ObjectId, ref: 'Family' }]
});
Yes, you will need to update it for both entities Person and Family if you want to maintain the uniformity. But, it could be done in one request/ mutation.
Well, you could perform the request depending upon the flow order of your business logic. Lets say if "Homer" is a Person who is a new member of the Simpson Family.
So, in that case you would add "Homer" to the Family collection(table) and then push the
ObjectId of this Simpson (Family collection) to the Person entity.
I have added the sample example of adding Homer to the Simpson family below. Hope this helps :)
addNewFamilyMember: async (_, {personID, familyID}) => {
try{
let family = await Family.findOne({_id: familyID});
let person = await Person.findOne({_id: personID}); // created to push the objectId of the family in this
if (!family){
throw new Error ('Family not found !')
} else {
let updatedFamily = await Family.findByIdAndUpdate(
{ _id: familyID },
{
"$addToSet": { // "addToSet" inserts into array only if it does not exist already
persons: personID
}
},
{ new: true }
);
person.families.push(updatedFamily); // pushing objectId of the Simpson family in Person "Homer"
person = await person.save();
updatedFamily.persons.push(person); // pushing "Homer" in the Simpson family
updatedFamily = updatedFamily.save();
return updatedFamily;
}
} catch(e){
throw e;
}
}
If you want to perform update, then it depends upon the intent of your purpose (as for example, if you just want to update the name "Homer", you would only have to update it in the Person collection, as the Family collection already has reference to the objectId of Homer, so every time you make an update to the Homer, the updated document would be referenced by Family collection ! ), and
if you want to perform deletion, then in that case too, the approach would be different based upon the scenario, as if you wish to remove a person document, or just remove the person reference from the family, or remove the family reference from the person !!
Lets say you want to delete a person then in that case, you would have to take the personId and search for that person and since you have access to the families via this person, you can access the families via person.families and remove the personId from those respective families as well ! And then you could remove the associated user too as you have the reference to the user too from the same person object.
To sum up, it depends upon your choice of action, and how much sanitization you want in your schema.. The above mentioned process would be just different in case if we take a different approach.

Related

Mongoose:create model using "1" as value for Schema.Types.ObjectId

What im tryng to do is the following, i have my models defined as:
const channelSchema= new Schema({
name:{type: String},
country_id:{type: Schema.Types.ObjectId, ref: 'country'}
})
and
const countrySchema = new Schema({
_id{ type : Number }
name: { type: String },
gmt: { type: String }
})
now, when i create a new countrySchema, i use a personalized "_id", such as 1 or 2, always a number and so on, this is created with 0 errors.
My problem is when i try to create a new channel schema, using
country_id = "1"
or
country_id = 1
i get the error:
'Cast to ObjectID failed for value "1" at path "country_id"'
what i've read from mongoose documentation, ObjectId is created (at default) with 12bytes.
My question is: Is there a way to evade having to use 12bytes keys, and to use "1" as ObjectId so i can populate channels with countries?
IMPORTANT: im using Node.Js, mongoose and express
If you want to reference a relationship, you need to set the same type to both sides of a relation. In this case _id of country is declared as Number, so country_id should be Number as well
country_id: { type: Number, ref: 'country' }
You could use "id" instead of "_id".

Mongoose/ MongoDB Database Design

I always have a certain fixed structure in my model (GroupName) and a dynamic part of 1-x (Members).
Group1
GroupName
Member 1
Member 2
Group2
GroupName
Member 1
Group3
GroupName
Member 1
Member 2
Member 3
Is it better to use two tables and connect them later via ids like this:
Groups:
Group1
GroupName
GroupId
Group2
GroupName
GroupId
Members:
Member 1
GroupId
Member 2
GroupId
or to use Schema.Types.Mixed(or anything else)? And how to do it in the second way?
I will always use them in combination later. From a gut feeling I would choose the first method:
http://blog.mongolab.com/2013/04/thinking-about-arrays-in-mongodb/
EDIT:
But even on the second method I have the issue, that one member can belong to multiple groups and I don't want to store him twice. The groups are unique and do only exist once.
But I'm new to MongoDb so I want to learn what's the best option and why.
EDIT II:
I have choosen two divide it into two docs. Is this implementation of the Schemas than correct like this:
var mongoose = require('mongoose');
// define the schema for group model
var groupSchema = mongoose.Schema({
href: {
type: String,
required: true,
unique: true
},
title: String,
members: [id: Schema.Types.ObjectId, name: String]
});
// create the model for users and expose it to our app
module.exports = mongoose.model('group', groupSchema);
&&
var mongoose = require('mongoose');
// define the schema for member model
var memberSchema = mongoose.Schema({
id: {
type:Schema.Types.ObjectId,
required: true,
unique: true
},
amount: String,
name: String
});
// create the model for users and expose it to our app
module.exports = mongoose.model('member', memberSchema);
There is an excellent post on the MongoDB blog which tells us about the various ways a schema can be designed based on the model relationships.
I believe the best schema for you would be to make use of embedded arrays with the member IDs.
//Group
{
_id: '1234',
name: 'some group',
members : [
'abcd',
'efgh'
]
}
EDIT
There is a correction needed in the schema:
// define the schema for group model
var groupSchema = mongoose.Schema({
href: {
type: String,
required: true,
unique: true
},
title: String,
members: [{id: Schema.Types.ObjectId, name: String}] //Needs to be enclosed with braces
});
// create the model for users and expose it to our app
module.exports = mongoose.model('group', groupSchema);
I don't know what your documents contains and if members are a growing array - for example Group1 can have 1-n members in any given moment . if this is the case you should go with option 2: try something like:
{gId: 1, mId: 5}
That is a design best suited for Social graph. Your Group documents will have a fixed size which is good for memory and you can easily get all the members of a group (just don't forget to index gId and mId)
If for each group there is a fixed number of members (or not growing and shrinking to much) then go with option 1
There is a great post by mongoDb team (and also src code) that talks about design.
Socialite

Mongoose Populate Returning null due to Schema design?

I'm stuck with mongoose populate returning null. I have a very similar situation to another question where it seems to we working just fine, perhaps with one important difference:
The model I'm referencing only exist as a subdocument to another model.
Example:
// The model i want to populate
Currency = new Schema({
code: String,
rate: Number
});
// The set of currencies are defined for each Tenant
// A currency belongs to one tenant, one tenant can have multiple currencies
Tenant = new Schema({
name: String,
currencies: [Currency]
});
Product = new Schema({
Name: String,
_currency: {type: ObjectId, ref: 'Currency'},
});
Customer = new Schema({
tenant: {type: ObjectId, ref: 'Tenant'},
products: [ Product ]
});
Then I export the models and use them in one of my routes where what I would like to do is something like
CustomerModel.find({}).populate('products._currency').exec(function(err, docs){
// docs[0].products[0]._currency is null (but has ObjectId if not usinn populate)
})
Which is returning null for any given product._currency but if I don't populate i get the correct ObjectId ref, which corresponds to an objectId of a currency embedded in a tenant.
I'm suspecting I need currencies to be stand-alone schema for this to work., Ie not just embedded in tenant, but that would mean I get a lot of schemas referencing each other.
Do you know if this is the case, or should my set-up work?
If this is the case, I guess I just have to bite the bullet and have multitude of collections referencing each other?
Any help or guidance appreciated!

Mongoose findOne embedded document by _id

I am trying to push menus to the embedded document. But I am getting not defined findOne in restaurant. I just want to push some documents into the restaurant's menu categories. As you can see in the schema:
var RestaurantSchema = new mongoose.Schema({
contactTelphone : String,
address : String,
branchID : String,
email : String,
restaurantName : String,
userID : String,
menuCategory : [MenuCategorySchema]
});
var MenuCategorySchema = new mongoose.Schema({
menuCategory : String,
menuCategoryAlt : String,
sequence : Number,
menus : [MenuSchema],
restaurantInfo : { type: Schema.Types.ObjectId, ref: 'Restaurant' },
});
var MenuSchema = new mongoose.Schema({
foodName : String,
foodNameAlt : String,
picName : String,
price : String,
rating : Number,
menuSequence : Number,
category : { type: Schema.Types.ObjectId, ref: 'MenuCategory' },
});
exports.menuForMenuCategory = function(newData, callback)
{
console.log(newData);
var menuCategoryId = newData.menuCategoryId;
var restaurantId = newData.restaurantId;
var newMenu = new Menu({
foodName : newData.foodName,
foodNameAlt : newData.foodNameAlt,
picName : newData.picName,
price : newData.price,
cookingCategory : newCookingCategory,
dishSpecial : newDishSpeical
});
Restaurant.findOne( {'_id' : restaurantId }, function(err, restaurant){
if (!err) {
//Is it how to do this? It says "findOne not defined"
restaurant.findOne( "_id" : menuCategoryId, function(err, category){
category.push(newMenu);
});
}
});
}
Subdocuments have an .id() method so you can do this:
myModel.findById(myDocumentId, function (err, myDocument) {
var subDocument = myDocument.mySubdocuments.id(mySubDocumentId);
});
See http://mongoosejs.com/docs/subdocs.html for reference.
restaurant is just an object containing the result to your query. It does not have a findOne method, only Restaurant does.
Since, MenuCategory is just a sub-document of Restaurant, this will come pre-populated whenever you retrieve a restaurant. E.g.
Restaurant.findById(restaurantId, function(err, restaurant){
console.log(restaurant.menuCategory);
// Will show your array of Menu Categories
// No further queries required
});
Adding a new Menu Category is a matter of pushing a new MenuCategory instance to the menuCategory array and saving the restaurant. This means the new menu category is saved with the restaurant and not in a separate collection. For example:
Restaurant.findById(restaurantId, function(err, restaurant){
// I'm assuming your Menu Category model is just MenuCategory
var anotherMenuCategory = new MenuCategory({
menuCategory: "The name",
menuCategoryAlt: "Alternative name",
sequence: 42,
menus: []
});
restaurant.menuCategory.push(anotherMenuCategory);
restaurant.save(); // This will save the new Menu Category in your restaurant
});
Saving a menu to a menu category follows the same procedure since, according to your schema, Menus are embedded sub-documents within each MenuCategory. But note that you need to save the restaurant as its the restaurant collection that is storing all your menus and menu categories as sub-documents
Having answered your question (I hope) I should also point out your schema design should be rethought. Having sub-documents nested within sub-documents is arguably not a good idea. I think I can see where you're coming from - you're trying to implement a SQL-like many-to-one association within your schemas. But this isn't necessary with NoSQL databases - the thinking is somewhat different. Here are some links to some SO questions about efficient schema design with NoSQL databases:
MongoDB Schema Design - Many small documents or fewer large
documents?
How should I implement this schema in MongoDB?
MongoDB relationships: embed or reference?

self referencing schema in mongoose

I've been through several tutorials. I'm still wondering what the best approach for my problem would be. I got the following Schema:
var userSchema = new Schema({
_id : Number,
first_name : String,
last_name : String,
friends : [ Number ],
messages : [{
from: Number,
body : String,
date : { type : Date, default: Date.now}
}]
}, { collection : "user"});
In friends I want to store the ids of user's friends in an array. In message.from I want to store the sender's id of a message.
Ideally I want those ids in friends and message.from to be only ids of valid user entries.
Unfortunately mongodb doesn't enforce referential integrity.
This functionality must be provided by your application.
So in your case: when a user is deleted your application must also remove references to that user in all other user's friends arrays and message fields.

Resources