Mongoose findOneAndUpdate cast error with custom _id - node.js

I have my Person schema like this :
const schema = new mongoose.Schema({
_id: Number,
name: String,
birthday: Date,
sex: String
});
schema.pre('findOneAndUpdate', async function (next) {
try {
let counter = await Counters.findByIdAndUpdate('person',
{
$inc: {
value: 1
}
},
{ new: true}
);
this._update._id = counter.value;
next();
}
catch (err) {
next(err);
}
});
The problem is when I try to add some new persons with findOneAndUpdate and upsert: true, it generates a CastError: Cast to ObjectId failed for value "18" at path "person".
My _id is defined as a Number so I don't understand why it's trying to cast it to an ObjectId ?
Update :
I found my problem, the Person model is referenced in some other model but I forgot to change the ref type in the other model...
person: {
type: Number, //HERE
ref: 'person',
required: true
}

You can change the type of the_id property although ins't a good approach, but actually you can't change the value since it's immutable and represents the primary key of the document. Keep in mind that _id is very important for MongoDB life cycle, like indexing. If you aim to change an Entity key, you can create other property, something like person_id.

_id is an auto generated property for MongoDB. If you want to add try a different name for the Id attribute like "personId" or you can use the auto generated Id by MongoDB without creating a seperate Id.

Related

schema option _id: false and document must have an _id before saving error happening

I am trying to create a user document through this way:
// create the document ---------------
const id = mongoose.Types.ObjectId()
let userDoc = await Admin.create({ ...req.body, _id: id, by: id })
Schema:
adminSchema = new mongoose.Schema({
// some other fields, firstName, lastName ... etc
by: {
type: mongoose.Schema.ObjectId,
ref: 'Admin',
required: [true, "the 'by' field is required"],
immutable: true,
}
}, { _id: false })
Model:
const Admin = mongoose.model('Admin', adminSchema, 'users')
My schema doesn't have an _id property.
Now I want to have the _id field and the by field has the same value, which is a server-side generated id.
Mongoose is throwing this error:
Error: MongooseError: document must have an _id before saving at
.../node_modules/mongoose/lib/model.js:291:18
update:
I updated my question, I added the schema options, and now I know the reason why this error is happening. it's because of the _id: false schema option that I have set. But I need this option because I don't want to see _ids in the responses that I send to the clients. is there a workaround? because this option looks like its doing two unrelated things
Using Mongoose 6.4
I solved this by removing the _id: false schema type option.
and to remove the _id from the responses without having to pollute the routes with _.omit()s or deletes everywhere, I added the following schema type options to the schema:
toObject: {
virtuals: true,
transform(doc, ret) {
delete ret._id
},
},
Now the real question is, why does simply adding the option _id: false results in the Mongoose error when you're generating the id on the server-side without the help of Mongoose?
Error: MongooseError: document must have an _id before saving at
.../node_modules/mongoose/lib/model.js:291:18
I partially answered my own question, but for this one... I really don't know.
Based on your comment, if you want the response the user receives to not contain the _id you can:
Get the document
Remove the _id property and return this object without the _id (Or create a new object to avoid problems).
A brief example could be:
let responseDoc = await Admin.findOne({ _id: id });
delete responseDoc["_id"]
// responseDoc is now ready for use. Note that if I am not mistaken it is still a document.

mongodb query events done to an item and group them into an array

I know that the title might be a bit vague, but I will elaborate here.
basically what I am trying to achieve is the following:
I have a collection with documents that have the following scheme:
bookId: <uuid>
genre: <string>
isTaken: true
historyIndex: each time something happens i increment this
returnedAt: not required but will be present on documents with historyIndex
takenAt: not required but will be present on documents with historyIndex
there are documents with no historyIndex field because they are representing the book itself without the action that were done to the book.
what i want to do is this:
I want to query the books by their unique uuid and then use the documents with historyIndex and add them to the main documents as in an array as called bookEvents
so the final results will be
bookId:
bookEvents: [] --> an array with all the entries that contain history index
basically everytime the status of the book changes, i am inserting an event with the date it was taken on and the date it was returned on
What would be the best way of achieving such thing ?
Should I query once and iterate in my code ?
Should I query twice (once where the fields exist and once where they don't) ?
Thank you in advance for all the people that will try to help!
You can use the plugin or events to achieve this.
var CounterSchema = new mongoose.Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
sort: {type: String}
});
entitySchema.pre('save', function(next) {
var doc = this;
counter.findByIdAndUpdateAsync({_id: 'entityId'}, {$inc: { seq: 1} }, {new: true, upsert: true}).then(function(count) {
console.log("...count: "+JSON.stringify(count));
doc.sort = count.seq;
next();
})
.catch(function(error) {
console.error("counter error-> : "+error);
throw error;
});
});
refer https://stackoverflow.com/a/40137723/8201020

default value for ref collection in mongoose

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.

Mongoose save model with object field

I'm trying to save a mongoose model that has a schema with an object field. When I try to save I get the error below. What am I missing?
I suspect this might have something to do with how mongoose objects are not quite like standard javascript objects since they are altered. However, what confounds me is that I'm doing exactly the same thing in another section of my code with a schema that has a nested object field and that works.
What I've tried:
To test, I changed my schema to list resulttype and resultround as individual fields without nesting and when I did that it worked.
I also tried creating an object with key value pairs outside of my model and then pass that object to my model. That did not work either.
Schema
var ResultSchema = new mongoose.Schema({
event_id : mongoose.Schema.ObjectId,
event_name: String,
event_type: String,
resultdate : String,
resulttype: {
type: String,
round: Number
},
// resulttype: String,
// resultround: Number,
});
Model save:
var newResult = new ResultModel({
// objNewResult
event_id: req.body.eventid,//hidden field
event_name: req.body.eventname, //hidden field
resultdate: req.body.resultdate,
// resulttype: resulttypelist,
// resultround: resultroundlist,
resulttype: {
type: req.body.resulttypelist,
round: req.body.resultroundlist
}
});
newResult.save(function (err, result) {
if (err) {
console.log("SOMETHING WENT WRONG");
console.log(err);
} else {
console.log("SUCCESSFUL RESULT ADDITION");
}
});
Error:
ValidationError: results validation failed: resulttype: Cast to String failed for value "{ type: 'standard', round: '1' }" at path "resulttype"
type is a reserved keyword in Mongoose schemas. It's used to specify the type of the field. When you specify this:
resulttype: {
type: String,
round: String
},
Mongoose will consider the field resulttype to be a String. So you have to use another name than type.
type is a reserved key:
By default, if you have an object with key 'type' in your schema, mongoose will interpret it as a type declaration. Source.
So right now, resulttype is expected to be of type String. You can use a different key instead:
Schema:
resulttype: {
resultType: String,
round: String
},
Model Save:
var newResult = new ResultModel({
// ...
resulttype: {
resultType: req.body.resulttypelist,
round: parseFloat(req.body.resultroundlist),
}
});

Revert to default value in mongoose if field is set to null

I am using mongoose with nodeJS. Consider the following schema:
var PersonSchema = new mongoose.Schema({
"name": {type: String, default: "Human"},
"age": {type: Number, defualt:20}
});
mongoose.model('Person', PersonSchema);
var order = new Order({
name:null
});
This creates a new Person document with name set to null.
{
name:null,
age: 20
}
Is there anyway to check if the property being created/updated is null and if it is null set it back to default. The following declaration
var order = new Order();
order.name = null;
order.save(cb);
should create a new Person document with name set to default.
{
name:"Human",
age: 20
}
How would one implement this using NodeJs and mongoose.
Well there are a few way to go about this:
PRE-SAVE/PRE-VALIDATE hooks
Mongoose Middleware hooks
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.
PersonSchema.pre('save', function(next) {
if (this.name === null) {
this.name = 'Human';
}
next();
});
ENUM
Mongoose Validation and Mongoose Enums
Strings have enum, match, maxlength and minlength validators.
var PersonSchema = new mongoose.Schema({
"name": {type: String, default: "Human", enum: ['Human', 'NONE-Human']},
"age": {type: Number, defualt:20}
});
Update 1
If I have 20 fields where I want to check whether they are set to null, do I have to repeat this.field = default for all of them?
I guess you would have to.
What does NONE-Human in enums do? I could not find documentation for this online.
That is just an example of ENUM with enum you can only choose values that are specified in ENUM i.e. 'Name' can only have values of 'Human' or 'NONE-Human'.
A little late to answer this, but a more generic approach would be not to hardcode the fields again in the pre hook.
Instead let's use the schema itself to go over the fields and check for the fields that have default values and are explicitly set to null. In this case we set it to the default value.
PersonSchema.pre('save', function(next) {
const self = this;
Object.keys(this.schema.paths).forEach(function(key) {
if(self.schema.paths[key].options.default && self[key] === null) {
self[key] = self.schema.paths[key].options.default;
}
});
next();
});
An even later answer. I was looking for a simpler solution and came across this question. Thought I'd share what I ended up doing.
db.Person.create({
name: usersName || "Human",
age: usersAge,
})

Resources