For my project i've created an userSchema which simplified looks like the following:
var userSchema = new Schema({
_id: String,
screenname: {type: String, required: false, default: "equal _id"},
});
The user has an _id that is a string which also is his username.
Everything works so far until i tried to add an extra field screenname. What i want is when the user creates an account, his screenname equals the value of _id. Later he can adjust it but by default it should equal the value of _id. i've also tried :
screenname: {type: String, required: false, default: _id},
But than ofcourse _id is not defined.
How should i set the default value to equal another value ?
use the pre middleware explained here
userSchema.pre('save', function (next) {
this.screenname = this.get('_id'); // considering _id is input by client
next();
});
You can pass a function to default, following is a schema field excerpt:
username: {
type: String,
required: true,
// fix for missing usernames causing validation fail
default: function() {
const _t = this as any; // tslint:disable-line
return _t.name || _t.subEmail;
}
},
Related
Why do I get this error?When I don't have the Cnic field in my DB anymore.
Here is my schema....
const mongoose = require('mongoose')
const uuidv1 = require("uuidv1")
const crypto = require("crypto")
const { ObjectId } = mongoose.Schema
const clientSchema = new mongoose.Schema({
name:{
type:String,
trim: true,
required: true,
// match:[
// new RegExp('^[a-z]+$', 'i'),
// 'Name Should have alphabets'
// ]
},
phone:{
type: String,
trim: true,
required: true,
unique: true
},
email:{
type: String,
trim: true,
required: true,
unique:true
},
hashed_password:{
type: String,
required: true
},
salt:String,
created:{
type: Date,
default: Date.now
},
createdBy:{
type: ObjectId,
ref: "Admin"
},
updated: Date
})
clientSchema.virtual('password')
.set(function(password){
this._password = password
// generate a timestamp
this.salt = uuidv1()
// encrypt password
this.hashed_password = this.encryptPassword(password)
})
.get(function(){
return this._password
})
clientSchema.methods = {
authenticate: function(plainText){
return this.encryptPassword(plainText) === this.hashed_password
},
encryptPassword: function(password){
if(!password) return "";
try{
return crypto
.createHmac("sha1", this.salt)
.update(password)
.digest('hex');
}catch (err){
return ""
}
}
}
module.exports = mongoose.model("Client", clientSchema)
I have made some changes , before this I had the Cnic field which was Unique...I have deleted that field.When I create a first user it gets created successfully but when I create a second user it gives the error above in the title...What is happening and how to solve it..?
------------------------SOLUTION----------------------------------------
I dropped a collection in my DB and it worked for me. Detailed answer is available in the comment section provided my #Scott Gnile
What happens is that MongoDB does not store the schema in the collection as if it were a table in a SQL Database, but it does store the indexes.
When you made the field unique, MongoDb created an index on that collection for that field.
So after inserting one document without that field, an entry in the index list goes as null. When you insert another document without that field, you are trying to insert another document with the same value null in the collection, and it fails because that field is supposed to be unique.
I've done the step by step and took a screenshot for you, see it below:
If you had a lot of data in that collection, dropping it wouldn't be an option, so you could drop the index, and it would work fine.
First, you list the indexes of such collection
Then, you identify which index to drop
You drop the index
Then you insert the same document that previously failed
See it below:
But there's an intermediate solution if you want to have an optional field that is unique across documents. This means that such a field is not present all the time, but it has to be unique when it is.
The solution for that is to create a sparse index. In the image below, you'll see that:
A new index (sparse and unique) is created on the same collection
The index creation happens successfully, which wouldn't happen if there were duplicate values for the unique field.
A document without the unique field is inserted with no issues
A document is inserted with a value for the unique field with success
Another document is attempted to be inserted in the collection with the same value for the unique field as the previous document, and as expected, it fails
Then, finally, another document is successfully inserted with a different value for that field.
so i know there is something called TTL in mongo but i dont think it will work for what i want to do, i want to set a document from my schema where the default value is basic but when the customer pays his membership i want to set it to "plus" i did that query in my controller, but i want to know if there is a way to make this document when the value is "plus" to have an expiration time, like 1 week or 1 month, and when the time is out set it again to basic, there is the schema
const UserSchema = new Schema({
username: {type: String, required: true},
nombre_empresa: {type: String},
email: {type: String, required: true, unique: true},
password: {type: String, required: true},
tipo_cuenta: {type: String, required: true},
isNewUser: {type: String, default: 'basic'}
},
{
timestamps: true
},
{
typeKey: '$type'
});
and here the process when i change his status
userCtrl.renderMembershipSucess = async (req, res) => {
const status = 'plus'
const status_basic = 'basico'
const status_user = await User.findByIdAndUpdate(
req.user.id, {
$set: { isNewUser: status }
}
)
console.log(status_user)
res.render('users/sucess')
}
Rather than relying on a trigger that must fire and correctly update the user records, store the expiration and check it when determining if the user is plus or not.
For example, you might add a plusExpires field to the user schema with a default value of null. When you upgrade the user to plus, set plusExpires to the Date that it should no longer be valid.
Add an instance method to the schema to perform that check:
UserSchema.methods.isPlus = function() {
return this.isNewUser == "plus" && (( this.plusExpires == null ) || (this.plusExpires > new Date())
}
Then any time you need to test if a user is plus or not, just call User.isPlus() on the user object.
i dont think there mongodb provide a built-in way to do what you want, however you can run a cronjob every midnight to fetch your "plus" customers, check their membership and update it if necessary.
another way maybe to design your mongodb collections to leverage the TTL, you can separate the User and PlusMembership collections, when a user make a payment, insert a PlusMembership data with TTL
Let's say we have :
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: { type: string }
});
How can we make sauce mandatory if title === "Pasta" ?
The validation needs to work on update too.
I know that a workaround would be
Find
update manually
Then save
But the risk is that if I add a new attribute (let's say "price"), I forget to update it manually too in the workaround.
Document validators
Mongoose has several built-in validators.
All SchemaTypes have the built-in required validator. The required validator uses the SchemaType's checkRequired() function to determine if the value satisfies the required validator.
Numbers have min and max validators.
Strings have enum, match, minlength, and maxlength validators.
For your case you could do something like this
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: {
type: string,
required: function() {
return this.title === "pasta"? true:false ;
}
}
});
If the built-in validators aren't enough, you can define custom validators to suit your needs.
Custom validation is declared by passing a validation function. You can find detailed instructions on how to do this in the SchemaType#validate().
Update Validators
this refers to the document being validated when using document validation. However, when running update validators, the document being updated may not be in the server's memory, so by default the value of this is not defined. So, What's the solution?
The context option lets you set the value of this in update validators to the underlying query.
In your case, we can do something like this:
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: { type: string, required: true }
});
mealSchema.path('sauce').validate(function(value) {
// When running update validators with
// the `context` option set to 'query',
// `this` refers to the query object.
if (this.getUpdate().$set.title==="pasta") {
return true
}else{
return false;
}
});
const meal = db.model('Meal', mealSchema);
const update = { title:'pasta', sauce:false};
// Note the context option
const opts = { runValidators: true, context: 'query' };
meal.updateOne({}, update, opts, function(error) { assert.ok(error.errors['title']); });
Not sure if this answers your question. Hope this adds some value to your final solution.
Haven't tested it, pls suggest an edit if this solution needs an upgrade.
Hope this helps.
I have a user profile, I have a field of 'earning' and it look like this in the schema
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning'
}
This how do I make a default value for earning field when a new user is created? I can't do this
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning',
default: 0
}
I got error of
Cast to ObjectId failed for value "0" at path "earning"
What you are doing wrong here is trying to cast a number on an ID field. Since it's a reference of another object Id field, you can not set 0 to it. What you need to do is to set null when a user is created in db and initialize it with a null value of earning.
Like:
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning',
default: null
}
When instantiating a document based on a Schema which has a key of type 'ObjectId' and a ref to another collection, the only way that I've found to set a 'default' value is through the use of Mongoose middleware at the schema level as described here. For example, setting a comment's author to a default 'guest' document from a User collection when the author is not logged in might look like this:
// user document in MongoDB
{
_id: ObjectId('9182470ab9va89'),
name: 'guest'
}
// CommentSchema
const mongoose = require('mongoose')
const CommentSchema = mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
body: String
})
CommentSchema.pre('save', function (next) {
this.author == null ? this.author = '9182470ab9va89' : null
next()
})
module.exports = mongoose.model('Comment', CommentSchema)
This example uses the 'save' pre hook with the ObjectId hardcoded in the schema for demonstration purposes, but you can replace the hardcoding of the ObjectId with a call to your backend or however else you'd like to get that value in there.
As I understand earning is indication of how much user earn so it should be of type Number not ObjectId
so try to change your Schema to be
earning: {
type: Number,
ref: 'Earning',
default: 0
}
so you can use 0
Note: if you should use ObjectId for some reason so the answer of 'Haroon Khan' is the correct answer.
Is there any way to set a field with an "unmodifiable" setting (Such as type, required, etc.) when you define a new Mongoose Schema? This means that once a new document is created, this field can't be changed.
For example, something like this:
var userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unmodifiable: true
}
})
From version 5.6.0 of Mongoose, we can use immutable: true in schemas (exactly as the aforementioned answer on mongoose-immutable package). Typical use case is for timestamps, but in your case, with username it goes like this:
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
immutable: true
}
});
If you try to update the field, modification will be ignored by Mongoose.
Going a little further than what have been asked by OP, now with Mongoose 5.7.0 we can conditionally set the immutable property.
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
immutable: doc => doc.role !== 'ADMIN'
},
role: {
type: String,
default: 'USER',
enum: ['USER', 'MODERATOR', 'ADMIN'],
immutable: true
}
});
Sources: What's New in Mongoose 5.6.0: Immutable Properties and What's New in Mongoose 5.7: Conditional Immutability, Faster Document Arrays.
Please be aware that the documentation explicitly states that when using functions with update in their identifier/name, the 'pre' middleware is not triggered:
Although values are casted to their appropriate types when using update, the following are not applied:
- defaults
- setters
- validators
- middleware
If you need those features, use the traditional approach of first retrieving the document.
Model.findOne({ name: 'borne' }, function (err, doc) {
if (err) ..
doc.name = 'jason bourne';
doc.save(callback);
})
Therefore either go with the above way by mongooseAPI, which can trigger middleware (like 'pre' in desoares answer) or triggers your own validators e.g.:
const theOneAndOnlyName = 'Master Splinter';
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
default: theOneAndOnlyName
validate: {
validator: value => {
if(value != theOneAndOnlyName) {
return Promise.reject('{{PATH}} do not specify this field, it will be set automatically');
// message can be checked at error.errors['username'].reason
}
return true;
},
message: '{{PATH}} do not specify this field, it will be set automatically'
}
}
});
or always call any update functions (e.g. 'findByIdAndUpdate' and friends) with an additional 'options' argument in the form of { runValidators: true } e.g.:
const splinter = new User({ username: undefined });
User.findByIdAndUpdate(splinter._id, { username: 'Shredder' }, { runValidators: true })
.then(() => User.findById(splinter._id))
.then(user => {
assert(user.username === 'Shredder');
done();
})
.catch(error => console.log(error.errors['username'].reason));
You can also use the validator function in a non-standard way i.e.:
...
validator: function(value) {
if(value != theOneAndOnlyName) {
this.username = theOneAndOnlyName;
}
return true;
}
...
This does not throw a 'ValidationError' but quietly overrides the specified value. It still only does so, when using save() or update functions with specified validation option argument.
I had the same problem with field modifications.
Try https://www.npmjs.com/package/mongoose-immutable-plugin
The plugin will reject each modification-attempt on a field and it works for
Update
UpdateOne
FindOneAndUpdate
UpdateMany
Re-save
It supports array, nesting objects, etc. types of field and guards deep immutability.
Plugin also handles update-options as $set, $inc, etc.
You can do it with Mongoose only, in userSchema.pre save:
if (this.isModified('modified query')) {
return next(new Error('Trying to modify restricted data'));
}
return next();
You can use Mongoose Immutable. It's a small package you can install with the command below, it allows you to use the "immutable" property.
npm install mongoose-immutable --save
then to use it:
var userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
immutable: true
}
});
userSchema.plugin(immutablePlugin);