As a front-end developer, I would like to have some isomorphic object for two mongoose models.
Let's say I have a user profile:
const profileSchema = new Schema({
firstName: { type: String },
lastName: { type: String },
// example of difference between model schemas
__user: { type: ObjectId, ref: 'Users' },
}
And I would like to create a list of Contact, where each contact will have some of the same keys:
const contactSchema = new Schema({
firstName: { type: String },
lastName: { type: String },
__profile: {
type: ObjectId,
ref: 'Profiles',
unique: true,
},
comment: { type: String },
}
NOTE: Contact could be the both:
as a reference to the Profile
and as independent record in DB / document.
==============================
My question: which is the best way to, organize models on such a way, so
contact could be a reference to the profile
when similar Profile key, like firstName will be updated, contact firstName will be updated too
AVOID of next ref
await Contact.findById(SOME_ID).populate('__profile');
// result
{
firstName: '',
lastName: '',
__profile: {
firstName: 'Chuck',
lastName: 'Norris',
}
}
Desired result - keep contact "isomorphic" like:
{
firstName: 'Chuck', // the key value from profile
lastName: 'Norris', // the key value from profile
__profile: SOME_PROFILE_ID,
}
Is this possible?
P.S: in my app, I'm using refs and started to use discriminators approaches.
I'd approach this task either:
1) put all data inside 1 collection (e.g. Profile):
// Profile model
{
firstName: 'Chuck',
lastName: 'Norris',
contacts: [{
type: ObjectId,
ref: 'Profile',
unique: true,
}],
...all other properties
}
that way you will be able to store just contacts (e.g. when I want to add just a contact) and profiles with much more info.
2) or will use discriminators to create a base class (e.g. Contact) and build Profile model upon it:
const options = { discriminatorKey: 'userType' };
const Contact = mongoose.model('Contact', new mongoose.Schema({
firstName: String,
lastName: String
},
options)
)
const Profile = Contact.discriminator(
'Profile',
new mongoose.Schema(
{
contacts: [{
type: ObjectId,
ref: 'Contact',
unique: true,
}],
comments: []
},
options
)
);
that way you will be able to save Contacts and Profiles inside 1 collection and reference base class (Contact) for contacts inside Profile
Hope that helps!
In my case, complete usage of Mongoose discriminators did not give me an advantage, because discriminators give you an opportunity to:
They enable you to have multiple models with overlapping schemas on
top of the same underlying MongoDB collection.
As a result, by using discriminators approach, I will reive one collection
of:
profiles
And there will be a mix of users and contact profiles.
==============================
So I decided to use two approaches:
create BesaSchema for the profile
take advantage of Mongoose Subdocuments
RESULT:
// keys which are same for both user Profile and Contact
const Schema = require('mongoose').Schema;
const util = require('util');
function BaseProfileSchema(...args) {
Schema.apply(this, args);
this.add({
firstName: { type: String },
lastName: { type: String },
});
}
util.inherits(BaseProfileSchema, Schema);
// user Profile Model
const profileSchema = new BaseProfileSchema({
__user: {
type: String,
ref: 'users',
required: true,
unique: true,
},
});
const Profile = mongoose.model('profiles', profileSchema);
// Contact with profile as subdocument
const contactProfileSchema = new BaseProfileSchema();
const contactSchema = new Schema({
// Associations
__refProfile: {
type: Schema.Types.ObjectId,
ref: 'profiles',
index: {
unique: true,
sparse: true,
},
},
profile: contactProfileSchema,
});
const Contact = mongoose.model('contacts', contactSchema);
As a result, I'm having DB with next collections:
users
profiles
contacts
Both profiles and contacts.profile are IDENTICAL because I'm extending the base shared schema.
Moreover:
inside Contact I'm having different keys for real referenced profile (__refProfile which can NOT be edit by others) and contact.profile
profile inside connection can be edited ONLY when the contact was edited by itself
P.S: happy codding 👨💻🍻
Related
I am using mongoose and have two schemas, user and community:
UserSchema:
const UserSchema = new Schema({
name: String,
communities: [{ type: Schema.Types.ObjectId, ref: CommunityModel }],
}, { timestamps: true });
}
Community:
const CommunitySchema = new Schema({
name: String,
users: [
{ type: Schema.Types.ObjectId, ref: "User" }
]
}, { timestamps: true });
When user joins a community, I want to know when it happened, so I'd like to add a joinedAt or createdAt to each community in the UserSchema. How could I achieve it if it is a reference? Is it possible? And if not, what could be the alternative?
Thanks!
This is the correct answer:
AI solved it for me, here is the correct example:
import * as mongoose from 'mongoose';
const Schema = mongoose.Schema;
interface User {
name: string;
email: string;
communities: Community[];
}
interface Community {
name: string;
description: string;
users: User[];
}
const userSchema = new Schema({
name: String,
email: String,
}, {
timestamps: true,
});
const communitySchema = new Schema({
name: String,
description: String,
}, {
timestamps: true,
});
// Define the user_communities table using the communitySchema
const userCommunitySchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
community: { type: Schema.Types.ObjectId, ref: 'Community' },
joined_on: Date,
}, {
timestamps: true,
});
// Use the userCommunitySchema to create the UserCommunity model
const UserCommunity = mongoose.model('UserCommunity', userCommunitySchema);
// Use the userSchema to create the User model, and define a virtual property
// for accessing the user's communities
userSchema.virtual('communities', {
ref: 'Community',
localField: '_id',
foreignField: 'user',
justOne: false,
});
const User = mongoose.model<User>('User', userSchema);
// Use the communitySchema to create the Community model, and define a virtual property
// for accessing the community's users
communitySchema.virtual('users', {
ref: 'User',
localField: '_id',
foreignField: 'community',
justOne: false,
});
const Community = mongoose.model<Community>('Community', communitySchema);
The userSchema and communitySchema are then used to create the User and Community models, respectively. For the User model, a virtual property called communities is defined using the virtual method. This virtual property is used to specify how to populate the user.communities property when querying the database. The communitySchema also defines a users virtual property, which is used to populate the community.users property when querying.
I am using Mongoose for a content-app that stores exhibition reviews. I decided to use references rather than subdocuments, so each Exhibition stores an array of _Ids for it's Reviews and each Review stores the _Id of it's Exhibition.
I want to require both fields, but the problem I run into is, what order to create them in?
I don't like my work around because I use an empty array for thereviews when creating an Exhibition, then exhibition.reviews.push() if the subsequent Review creation is successful, but have to Exhibition.deleteOne() if it throws an error.
Is this robust or am I risking having Reviews with no Exhibition reference or Exhibitions with no Reviews ?
Review model:
const reviewSchema = mongoose.Schema({
content: {
type: String,
required: true
},
exhibition: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Exhibition',
required: true
}
})
const Review = mongoose.model('Review', reviewSchema)
Exhibition model:
const exhibitionSchema = mongoose.Schema({
title: {
type: String,
required: true
},
reviews: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Review',
required: true
}]
})
const Exhibition = mongoose.model('Exhibition', exhibitionSchema)
My work-around:
try {
const exhibition = await Exhibition.create({
title: 'title'
reviews: []
})
try {
const review = await Review.create({
review: 'review'
exhibition: exhibition._id,
})
exhibition.reviews.push(review._id)
await exhibition.save()
} catch {
Exhibition.deleteOne({ _id: exhibition._id })
}
} catch (err) {
return handleError(err)
}
})
Is there a better way to do this?
I am trying to create different types of registration for user . I have got three collection for users . I have been references user collection in both of teacher and student because I need to get email and password.
If a teacher register including email, password, firstname , lastname etc , there is a collection .
if a student register including email, password, firstname , lastname etc , there is another collection .
our all of email and password will be one collections
user - table/collection
- email : test#gmail.com
- password: asdfasdf
student - table /collection
- firstname: 'sujon"
teacher - table/collection
- firstname: "sujon"
const UserSchema = mongoose.Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
default: false,
},
})
const StudentSchema = mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
firstname: {
type: String,
},
lastname: {
type: String,
},
photo: {
type: String,
},
education: {
type: String,
},
birth: {
type: Date,
},
sex: {
type: Boolean,
},
})
const TeacherSchema = mongoose.Schema({
user: {
type: mongoose.Schema.ObjectId,
ref: "user"
},
firstname: {
type: String
},
lastname: {
type: String
},
photo: {
type: String
},
designation: {
type: String
},
birth: {
type: Date
},
sex: {
type: Boolean
}
});
how can implement database design
Creating a single User schema would be fine. You can have a single schema with all properties (since all three types of user have almost same properties) and then add a 'roles' field like this:
roles: [{ type: String }]
The roles field can have multiple roles [ 'Teacher', 'Student' ... ]. In this way a single user can have multiple roles for example a Teacher can also be an admin etc.
Also you won't have to create a new model whenever a new role is introduced.
Users can be queried based on their roles. The roles can then be used for authentication as well for example a user with role 'Teacher' can create assignment or a user with role 'Student' can submit assignments. When registering a user you can set some sort of model validation on the api or client side to accept a certain model.
**I have answered below. In short you need to require the Model in the module in which you wish to populate, even though you do not refer to it directly.
I am hitting a strange problem with mongoose when populating just one particular array of IDs.
I have three models, User, Company and Widgets.
When I return the company populated with the users all is fine using:
Company.findOne({ name: 'xyz' })
.populate('users')
.exec(function(err, company) {
if (err) return res.send(err)
res.send(company)
})
However when I try to replace populate 'users' with 'widgets' I get the following error:
{
"message": "Schema hasn't been registered for model \"widget\".\nUse mongoose.model(name, schema)",
"name": "MissingSchemaError"
}
Here are the models:
USER:
const UserSchema = new Schema({
name: String,
email: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
company: {
type: Schema.Types.ObjectId,
ref: 'company'
}
});
const User = mongoose.model("user", UserSchema);
COMPANY:
const CompanySchema = new Schema({
name: String,
URL: {
type: String,
unique: true
},
users: [{
type: Schema.Types.ObjectId,
ref: 'user'
}],
widgets: [{
type: Schema.Types.ObjectId,
ref: 'widget'
}]
});
const Company = mongoose.model('company', CompanySchema);
WIDGET:
const WidgetSchema = new Schema({
title: {
type: String,
required: true
},
maker: String
});
const Widget = mongoose.model('widget', WidgetSchema);
I have manually inspected the _ids in the widget array of the company model and they are all correct.
OK, so this was a lack of understanding on my behalf.
In the module where I was using:
Company.findOne({ name: 'xyz' })
.populate('users')
.exec(function(err, company) {
if (err) return res.send(err)
res.send(company)
})
I had imported the User model for other uses in the module. However, as I was not directly referring to Widget I had not imported it. Having done some more research I found that you need to import a model when populating even though not referring to it directly.
Let me know if best to delete whole thread or leave for reference.
Database: MongoDB
Backend: Node.js
There are two tables in the database: User and userDetails. ObjectId in User table has been used as a reference in userDetails table as userId. Now based on this userId I want to display userDetails.
I have tried to make a function and tried to test that in postman but I get null data. When I test this function in api testing tool postman, I don't get any user Details. I am not able to understand why I am not getting any data.
Here is the function:
function getUserDetailByUserId(req, res) {
userDetails.find(req.params.userId, (error, detail) => {
if (error)
res.send(error);
res.json(detail);
}).populate(userId);
}
use ObjectId
new mongoose.mongo.ObjectID(req.params.userId)
function getUserDetailByUserId(req, res) {
userDetails.findOne({userId: new mongoose.mongo.ObjectID(req.params.userId)})
.populate(userId)
.exec(function(error, detail){
if (error)res.send(error);
res.json(detail);
});
}
userDetail Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const userDetailSchema = new Schema({
dateOfBirth: {
type: Date,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
interest: {
type: String,
required: true
},
remarks: {
type: String,
},
userId: {
type: Schema.Types.ObjectId, // Referencing the Registered Users in User Model by their ObjectId reference.
ref: 'User' // Creating a relation between User and UserDetails Model through unique ObjectId.
}
});
module.exports = mongoose.model('UserDetails', userDetailSchema);