Purpose of 'isUnique' field in Mongoose - node.js

I don't understand the purpose of fields like unique and required in Mongoose schemas. In the case of unique, it appears that you have to write your own methods to query MongoDB to see if the value already exists. I suppose with 'required' Mongoose just needs to check if that value was passed in the constructor of a Mongoose model. But with unique, I don't understand the purpose of that.
userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
validate: [validation.usernameValidator, 'not a valid username']
},
...
in order to validate username, I have to create my own function = validation.usernameValidator to check MongoDB to see if the username already exists. So then what is the point of isUnique?

unique will create a MongoDB "unique" index on the property, preventing documents with the same value for that property to exist in the same collection.
However, since it's enforced by the database, the only way this works is by saving a document and catching the duplicate-key-error that MongoDB will return.
You can check for that error like so:
if (err && err.code === 11000) { ...duplicate... }

Related

Non-existing field in Mongodb document appears in mongoose findById() result

I'm somewhat new in what is related to Mongoose and I came to this behaviour I consider as strange. The document returned by Mongoose has fields that are not present in the actual MongoDb document, and seem to be added by Mongoose based on the schema.
I use a schema similar to this (this one is simplified) :
const ProfessionalSchema = new mongoose.Schema({
product: {
details: [{
_id: false,
id: String, // UUID
name: String,
prestations: [{
_id: false,
id: String, // UUID
name: String,
price: Number,
}],
}],
},
[...]
My document as shown in Mongodb with mongo CLI utility doesn't have a product field.
What I don't understand is why the result of Professional.findById().exec() returns a document with a product:{details[]} field. I expect not to have that field in the Mongoose returned result, since it is not present in the original MongoDb document.
The Mongoose documentation found https://mongoosejs.com/docs/guide.html (Schema and Model paragraph) didn't help.
My business logic would require that field not to be present, instead of being forced by the schema. Is this achievable ?
Try taking a look at the default option. You could e.g. default your product to null and then, in your business logic, handle the "product is null" case rather than the "product field does not exist" case.
As for why this is happening, it's because you're dealing with a schema. If the field doesn't exist on the document, it's going to be auto-populated. The whole point of a schema is to ensure consistency of your document structure.

node.js to check out duplication value in mongoose

Now I'd like to save my json data into mongoose but the duplicate value had to be filtered.
my_json = [
{"name":"michael","age":21,"sports":"basketball"},
{"name":"nick","age":31,"sports":"golf"},
{"name":"joan","age":41,"sports":"soccer"},
{"name":"henry","age":51,"sports":"baseball"},
{"name":"joe","age":61,"sports":"dance"},
];
Database data is :
{
"name":"joan","age":41,"sports":"soccer"
}
Is there some specific method to avoid duplicate data insert to mongoose directly? It might be saved 4 of values except "joan" value.
Once I suppose to try to use "for statement", it was fine.
However I just want to make a simple code for that what could happen in a variety possible code.
for(var i = 0; i < my_json.length; i++){
// to check out duplicate value
db.json_model.count({"name":my_json[i].name}, function(err, cat){
if(cat.length == 0){
my_json_vo.savePost(function(err) {
});
}
})
};
As you see I need to use count method whether the value is duplicated or not. I don't want to use count method but make it more simple..
Could you give me an advice for that?
You can mark field as unique in mongoose schema:
var schema = new Schema({
name: {type: String, required: true, unique: true}
//...
});
Also, you can add unique index for name field into your database:
db.js_model.createIndex( {"name": 1}, { unique: true, background: true } );
then, if new entity with the same name will be asked to save - mongo won't save it, and respond an error.
In Addition to #Alex answer about adding unique key on the name field.
You can use insertMany() method with ordered parameter set to
false. Like this...
let my_json = [
{"name":"michael","age":21,"sports":"basketball"},
{"name":"nick","age":31,"sports":"golf"},
{"name":"joan","age":41,"sports":"soccer"},
{"name":"henry","age":51,"sports":"baseball"},
{"name":"joe","age":61,"sports":"dance"},
];
User.insertMany(my_json ,{ordered :false});
This query will successfully run and insert unique documents, And also
produces error later after successful insertion. So You will come to
know that there were duplicate records But now in the database, all
records are unique.
Reference InsertMany with ordered parameter

Mongoose returns default value for field even when the field removed from the document

I have a school schema like this:
var SchoolSchema= new mongoose.Schema({
name: {type:String, required: true}
status: { type: String, default: "active" }
});
Mongoose default feature works on two levels:
1) Set field value to default while saving, if the field is not present in the input.
2) While fetching, set field value to default value, if the field is not present in the saved document.
What I wish is for it to set it to default value only while saving, and when fetching a document it should return null value for status if the status property is not present in the record. Currently it returns 'active' when I remove status property from the record.
Is there a way I could do this Mongoose?
What you can do is use the pre-save middleware.
It would look something like this:
schema.pre('save', function(next) {
// Change your fields
next()
})
This code will be activated when you save your mongo document.
Mongoose also adds a isNew field to your object so you could extend this code to only change your fields if this is the first time the document is being save to the DB
schema.pre('save', function(next) {
if (this.isNew) {
// Change your fields
}
next()
})

Mongoose find with default value

I have a mongoose model: (With a field that has a default)
var MySchema= new mongoose.Schema({
name: {
type: String,
required: true
},
isClever: {
type: Boolean,
default: false
}
});
I can save a model of this type by just saving a name and in mongoDB, only name can be seen in the document (and not isClever field). That's fine because defaults happen at the mongoose level. (?)
The problem I am having then is, when trying to retrieve only people called john and isClever = false:
MySchema.find({
'name' : 'john',
'isClever': false
}).exec( function(err, person) {
// person is always null
});
It always returns null. Is this something related to how defaults work with mongoose? We can't match on a defaulted value?
According to Mongoose docs, default values are applied when the document skeleton is constructed.
When you execute a find query, it is passed to Mongo when no document is constructed yet. Mongo is not aware about defaults, so since there are no documents where isClever is explicitly true, that results in empty output.
To get your example working, it should be:
MySchema.find({
'name' : 'john',
'isClever': {
$ne: true
}
})

Default value not set while using Update with Upsert as true

I have the following model for users:
var UserSchema = new mongoose.Schema({
name: String,
dob: Date,
sex: String,
photo: String,
email: {type: String, index: {unique: true, required: true}},
created: {type: Date, default: Date.now}
});
var User = mongoose.model('Users', UserSchema);
As you can see the 'created' field takes a default value of the current date so that it is automatically set when a new user is created.
I use the following query when user details are posted:
User.findOneAndUpdate({email: user.email}, user, {upsert: true}, function (err, user) {
if (err) {
return callback (err);
} else {
callback(null, user);
}
});
The purpose of using findOneAndUpdate with upsert: true is to either return an existing profile, or create a new one. It also updates any fields based on the data posted.
However, the created field gets updated with the current date each time, even though the created field is not posted. How can I make sure that this field is set only once?
EDIT
An example object from the database:
{
"_id" : ObjectId("54620b38b431d48bce7cab81"),
"email" : "someone#google.com",
"__v" : 0,
"name" : "somone",
"sex" : "male"
}
It turns out that the created field is not being set even while creating a new object using upsert. Mongoose just returns the current date based on the schema even though it does not exist in the document.
So, the question now becomes: How do I make sure that using upsert creates the default value for a field not supplied in the arguments?
For adding defaults to your document if it was created with findOneAndUpdate (it didn't exist before the query) and you did not provide the field in the update you should use setDefaultsOnInsert.
When upsert and setDefaultsOnInsert are both true, the defaults will be set if the record is not found and a new one is created.
This skips the workflow of having to check if the record exists and if not then creating a new one with 'save' just to make sure defaults are set.
I have had the same issue (record created with findOneAndUpdate with upsert: true) and the default value for a field was not added to the record, even though it was in the schema.
This is only in regards to adding defaults when using findOneAndUpdate to create documents, not for skipping the update of the 'created' field.
e.g.
User.findOneAndUpdate({email: user.email}, user, {upsert: true, setDefaultsOnInsert:true}, ...)
findOneAndUpdate simply sends a MongoDB findAndModify request (see findOneAndUpdate). What this means is that it skips all the mongoose magic involved with the schema setters, getters, defaults, etc. Validation is only run on create/save so the way around this is to do a .findOne(), check existence/create a new one, and then .save().
see this issue for more discussion
EDIT:
In regards to the first question about changing the date each time, you could change the schema a bit. Get rid of the default value, and instead add this after declaring the schema:
UserSchema.pre("save", function (next) {
if (!this.created) {
this.created = new Date();
}
next();
});
That will only create a date if the created: value is not present, and should prevent it from changing the creation date each time (when using .save()).
see Mongoose middleware

Resources