I'm very new to the NoSQL way of doing things so please excuse me if I'm just thinking about this completely wrong (I feel like I am).
My application has Users and Organizations, and the Users must have and belong to those Organizations with a role of member or owner.
Currently in my schema for my Users I have:
orgs: [{ type: Schema.Types.ObjectId, ref: 'Org' }]
and in my schema for my Organizations I have:
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
but I would also like to attach a role of member or owner to this.
Is this something I should put in the actual reference somehow like:
members: [{ type: Schema.Types.ObjectId, ref: 'User', role: String }]
or should the role be elsewhere in the schema? What is the proper way to handle this or is there a different way to schematize this that is more appropriate?
And once I have the schema set up for this properly, it would be helpful to have an example of how to create a few users/orgs with this roled ref.
You can do this with a small change to what you have there. I store messages in an app the same way (text and sender).
Here's what you could do:
members: [{
role: {
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}],
Then when you want to add a member or members to an organization (assume you have already created an organization and a user separately):
function addNewMember(org_id, user_id, role) {
var member = {role: role, user: user_id};
Organization.findByIdAndUpdate(org_id, {"$push": {"members":member}}, function(err,org) {
...
});)
// you can use the $each clause to add a whole array of members at the same time too
// like so:
// {"$push": {"members":
// {
// "$each": [memberarray]
// }}}
}
And when you want to find an organization including it's members, you could do:
Organization.findById(org_id)
.populate('members.user')
.exec(callbackfunction);
Think carefully if you do absolutely need to duplicate the Organization -> member relationship for each member too as Member -> organizations. If this is indeed important, after successfully adding the user to the organization, in the callback you can do a second query to update the user's orgs field just like above.
Related
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.
I am working on a project and have these two MongoDB collections, team (holding the details of teams) and payment (holding the payment of the teams) (strictly 1-1 relationship).
Payment Schema
{
...
team: { type: Schema.Types.ObjectId, ref: 'Team', unique: true },
...
}
For Team, I have two alternatives:
Team1 Schema
{
user_id: { type: Schema.Types.ObjectId, ref: 'User' }
}
...
Team2 Schema
{
user_id: { type: Schema.Types.ObjectId, ref: 'User' }
payment: { type: Schema.Types.ObjectId, ref: 'Payment', unique: true }
}
NEED: I have a component "My Teams" where I need to show logged-in user's all teams and his payment status (yes/no).
ISSUE WITH Team1 Schema: Since I do not have reference to Payment so I need to make another call to backend with team's _id to get Payment object for every team. If a user has 10 teams then it will be 11 backend calls (1 for teams, next 10 for their payment statuses).
ISSUE WITH Team2 Schema: Since I now have Payment _id inside the Team2 Schema so I can simply check if that field exist or not to determine if it's paid or not. But now the issue is, when a payment is made, I need to update both of Collections and need to use Transactions (to rollback in case any fails) which increases complexity and is also not support unless I have replica sets set upped.
Can you please help me figuring out this the best way possible?
Thanks in advance.
The simplest solution is just to have team_id in payment schema (which you already have).
You neither need user_id nor payment_id in team schema to get payments with team.
You could just have an aggregate query with lookup on the payments table to get the team along with payment.
So, considering you have an ID of teams and you need the teams data along with the payments data, you could write an aggregation query, something like this,
Team.aggregate([
{
$match: { _id: { $in: list_of_user_ids } } // this will get the teams which match the array of ids
},
{
$lookup: // this will search data from a different collection
{
from: 'payments', // the collection to search from
localField: '_id', // the matching field in the team collection
foreignField: 'team', // matching field in the payment colection
as: 'payment' the name you want to give to the resulting payment object
}
}
])
Edit 1:
The lookup I've written does exactly what you need. Just that I assumed you had an array of user Ids. If you have a single user ID, just change the match operation to what you've written
$match: { user_id: currently_loggedin_userId }
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
There is a similar thread # Mongoose variable key name. However, he goes with another method instead of solving this. The other method is the OR part of my title.
EDIT - IGNORE THIS SECTION, I AM USING THE ALTERNATIVE NOW. Issue now lays with referencing a child's Object ID elsewhere.
I have the following array:
selections: [{
4433d18d31f3775756ac2a70: "542e91aa31f3775756abccda"},
{4433d18d31f3775756ac2a71: "542e916c31f3775756abccd8"},
{4433d18d31f3775756ac2a72: "542e934231f3775756abccdb"
}]
My schema is currently as follows:
selections: {
<something>: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
In place <something>, is there a way of essentially saying "I don't care what's here"?
ALTERNATIVELY, as this doesn't seem possible after scouring the internet, I can have the following schema:
selections: {
pid: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Competition.CHILD'
}
selection: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
But the issue here is that ObjectID that is being used where <something> is a child schema inside of the schema Competition, and I can't find a way of saying that for the Object ID.
Any advice/help would be great please. Ideally I'd prefer the first solution but I understand it may not be possible. Thanks!
Use an array of objects with optional (but fixed) keys:
selections: [{
selection: {type: ObjectId, ref: 'Selection'},
child: {type: ObjectId, ref: 'Competition.CHILD'}
}]
This will enable you to do better queries, use mongoose population, etc.
I have an idea for how to store the relationships. Each user has a friends Array filled with IDs. However, how should I initiate a friend request in my Express.js app in MongoDB?
I'm thinking about creating a "notifications" collection with:
_id, userId, type, friendId, read
So when the requested friend logs in, they can see all of their own notifications to deal with...
Or is that ridiculous?
For such notifications what I did, is as follows:
var notificationSchema = mongoose.Schema({
to:{
type: mongoose.Schema.Types.ObjectId, ref: 'User'
},
from:{
type: mongoose.Schema.Types.ObjectId, ref: 'User'
},
for: {
type: String
},
status: {
type: String,
default: "Not Seen"
},
description:{
type: String
}
},{ timestamps: { createdAt: 'created_at' }
});
where I have saved _id of to and from (users) where I used for field for notification type like following, liked etc and used description field as optional.
Sounds reasonable enough. But my approach will be a little different. I would store the notifications in the user db itself. Something like
username
dob
...
notifications[[type: 'friend', read: 0, request_from: '22sd300sdf45003425asz'], ...]
...
This way, you don't have to make a db call on every page load. As soon as you initialize a session (I use passport), it will be there, ready already for templates. After a valid action from the user, I can delete it or whatever.
But again, its dependent on the need. Do what suits you best!
If you store it in the user passport session (solution mentioned earlier) you will not be able to receive anymore notifications since it is static information in the header and not connected directly to the document store.
The best way to do it would to have let it have it's own store.
{Id:ObjectId, for_user:String, sent from:String, status:Boolean}
Perhaps you can initially set the status to null then set it to true or false when a user accepts or denies it. After create the user to user friend relationship. That's more or less the way I would go about it.