how to validate dynamic key of mongoose schema - node.js

I am trying to build a MEAN project, so I need to validate some of my model's dynamic key...
I want to create a Schema like this
var exampleSchema = new Schema({
x: {
type: String,
default: '',
required: true,
trim: true
},
y: {}
});
as you see I have mixed type object, but actually it is a Language Map and it should be something like this,
{
"en-US": "answered"
}
can I validate my key with mongoose? (I think it has no function like that)
if no, how and where can I validate it (in model or controller)?

You may want to look into this: http://mongoosejs.com/docs/middleware.html
Specifically pre-save events. Mongoose gives you control over this and you can perform validation, mapping as needed before the actual model gets saved.
Also works nice for pre-init event if you need defaults such as "current date" for an audit trail such as "createdOn: date".

Related

Storing site config as Mongoose model

I have website configuration (currently stored as JSON file), and I would like to move it to MongoDB and probably use Mongoose to handle read-write operations and perform validation through schemas.
Configuration is an object with limited amount of keys, similar to that:
{
siteOffline: false,
storeOffline: false,
priceMultipliers: {
a1: 0.96
a2: 0.85
},
...
}
Should it be made a collection with key-value entries? Not sure how to enforce Mongoose schema in this case.
Should it be made a collection with a single document? Not sure how to guarantee that there is only one document at a time.
Any other options?
Ok, one thing at a time :
if you want to use mongoose, you should have your full config as one document :
var siteConfig = new Schema({
siteOffline: Boolean,
storeOffline: Boolean,
priceMultipliers: {
a1: Number
a2: Number
}
});
Then, if you want a one document only collection, you can use MongoDB capped collections
I suggest you go through the multiple options that Mongoose allows, here
The Schema for your one-doc collection would be something like :
var config = new Schema({
siteOffline: Boolean,
storeOffline: Boolean,
priceMultipliers: {
a1: Number
a2: Number
}
}, {
collection:'config',
capped: { size: 1024, max: 1}
});
There's some irony in having a "collection" of only one document though :)
Another "hack" solution that could work better for you is to use a secured field (of which you cannot change the value), and add a unique index on that field. Read this for more info The field could be present in the document but not in the model (virtual). Again, this is hacky, but your use case is a bit strange anyway :)
The capped approach works but it doesn't allow updating fields.
There is a better solution using the immutable field property (>= mongoose v5.6).
In addition, you can add the collection option to your schema to prevent mongoose from pluralize the name of the collection
let configSchema = new Schema(
{
_immutable: {
type: Boolean,
default: true,
required: true,
unique : true,
immutable: true,
},
siteOffline: Boolean,
storeOffline: Boolean,
priceMultipliers: {
a1: Number
a2: Number
},
},
{
collection:'config',
}
);
var Config = mongoose.model( 'config', configSchema );
Config.updateOne( {}, { siteOffline: false });
I hope it will help

Execute query in Mongoose schema validation

Im trying to setup my MongoDB design in such a way that there is a projects collection, and a people collection. The Project model schema contains a _people item, which references the People model. (As opposed to the People model having a field to reference the project he/she belongs to. It needs to be like this)
I need to run a validation whenever a new document is created in the people container, that there can be only one manager per a project. This would be very easy if it was possible for me to execute a query for the elements validation in the schema, but I don't believe thats possible...
Heres the schema for the People model currently:
const peopleSchema = new Schema( {
name: {
type: Schema.Types.String,
required: true,
minlength: 3,
maxlength: 25,
trim: true,
select: true
},
isManager: {
type: Schema.Types.Boolean,
default: false,
validate: {
validator: function ( v ) {
// How can I check if there are any existing `people` documents with the
// `isManager` set to true, which are referenced by the same project.
// If I can return a promise from here, then I can just execute a query and verify the results
},
message: 'There can be only one manager per each group'
}
}
})
As you can see in the isManager.validate.validator function, I noted that if this documents isManager is set to true, I need to find a way to check that there isn't already a person document referenced by the same project that is also a manager.
Knowing which project is referencing this document isn't an issue, I will have that somewhere, I just need to know how to run a query.. is that possible?
I was able to accomplish the desired effect by using Mongooses Middleware functionality. Setting up the validation inside the pre-save hook worked just fine

Easy way to reference Documents in Mongoose

In my application I have a User Collection. Many of my other collections have an Author (an author contains ONLY the user._id and the user.name), for example my Post Collection. Since I normally only need the _id and the name to display e.g. my posts on the UI.
This works fine, and seems like a good approach, since now everytime I deal with posts I don`t have to load the whole user Object from the database - I can only load my post.author.userId/post.author.name.
Now my problem: A user changes his or her name. Obviously all my Author Objects scattered around in my database still have the old author.
Questions:
is my approuch solid, or should I only reference the userId everywhere I need it?
If I'd go for this solution I'd remove my Author Model and would need to make a User database call everytime I want to display the current Users`s name.
If I leave my Author as is, what would be a good way to implement a solution for situations like the user.name change?
I could write a service which checks every model which has Authors of the current user._id and updates them of course, but this sounds very tedious. Although I'm not sure there's a better solution.
Any pro tipps on how I should deal with problems like this in the future?
Yes, sometime database are good to recorded at modular style. But You shouldn't do separating collection for user/author such as
At that time if you use mongoose as driver you can use populate to get user schema data.
Example, I modeling user, author, post that.
var UserSchema = new mongoose.Schema({
type: { type: String, default: "user", enum: ["user", "author"], required: true },
name: { type: String },
// Author specific values
joinedAt: { type: Date }
});
var User = mongoose.model("User", UserSchema);
var PostSchema = new mongoose.Schema({
author: { type: mongoose.Scheam.Types.ObjectId, ref: "User" },
content: { type: String }
});
var Post = mongoose.model("Post", PostSchema);
In this style, Post are separated model and have to save like that. Something like if you want to query a post including author's name, you can use populate at mongoose.
Post.findOne().populate("author").exce(function(err, post) {
if(err)
// do error handling
if(post){
console.log(post.author.type) // author
}
});
One solution is save only id in Author collection, using Ref on the User collection, and populate each time to get user's name from the User collection.
var User = {
name: String,
//other fields
}
var Author = {
userId: {
type: String,
ref: "User"
}
}
Another solution is when updating name in User collection, update all names in Author collection.
I think first solution will be better.

Validation errors in custom instance or static methods in a Mongoose model

I have a basic Mongoose model with a Meeting and Participants array:
var MeetingSchema = new Schema({
description: {
type: String
},
maxNumberOfParticipants: {
type: Number
},
participants: [ {
type: Schema.ObjectId,
ref: 'User'
} ]
});
Let's say I want to validate that the number of participants added doesn't exceed the maxNumberOfParticipants for that meeting.
I've thought through a few options:
Custom Validator - which I can't do because I have to validate one attribute (participants length) against another (maxNumberOfParticipants).
Middleware - i.e., pre-save. I can't do this either because my addition of participants occurs via a findOneAndUpdate (and these don't get called unless I use save).
Add validation as part of my addParticipants method. This seems reasonable, but I'm not sure how to pass back a validation error from the model.
Note that I don't want to implement the validation in the controller (express, MEAN.js stack) because I'd like to keep all logic and validations on the model.
Here is my addParticipants method:
MeetingSchema.methods.addParticipant = function addParticipant(params, callback) {
var Meeting = mongoose.model('Meeting');
if (this.participants.length == this.maxNumberOfParticipants) {
// since we already have the max length then don't add one more
return ????
}
return Meeting.findOneAndUpdate({ _id: this.id },
{ $addToSet: { participants: params.id } },
{new: true})
.populate('participants', 'displayName')
.exec(callback);
};
Not sure how to return a validation error in this case or even if this pattern is a recommended approach.
I wouldn't think that's it's common practice for this to be done at the mongoose schema level. Typically you will have something in between the function getting called and the database layer (your schema) that performs some kind of validation (such as checking max count). You would want your database layer to be in charge of just doing simple/basic data manipulation that way you don't have to worry about any extra dependencies when/if anything else calls it. This may mean you'd need to go with route 1 that you suggested, yes you would need to perform a database request to find out what your current number of participants but I think it the long run it will help you :)

Mongoose Model Options field

Hi I just started playing with Mongoose. It seems pretty awesome!
Now coming from a Django background, how would one implement a type of options field like:
STATUS_OPTIONS : [{"Open",1},{"Closed",2},{"Pending",3"}]
status: { type:String, required:true, options:STATUS_OPTIONS },
So that it can be set like status = Open or something like that.
Or should this just be a normal String field and I set it accordingly in my app?
You can constrain a Mongoose schema string field to a set of enumeration values with the enum attribute:
var s = new Schema({
status: { type: String, enum: ['Open', 'Closed', 'Pending'] }
});
What you may be trying to do is reference some possibilities, right? Probably like an enum field type.
Well, you may have better luck using directly an String or using another Schema (but if you only need the strings Closed, Open, Pending, this wouldn't be needed).

Resources