Mongoose Saving _id's as Numbers - node.js

I would like to save an _id as a Number as seen in this documentation:
_id : Number,
Taken from here: http://mongoosejs.com/docs/populate.html
However, when using this code, I recieve no errors when saving data to the Model. If I remove this line it saves without failure as an ObjectID
My code:
var UserSchema = new Schema({
_id: Number
});
mongoose.model('User', UserSchema)

Mongoose automatically assigns an _id to every document you create being consistent with general MongoDB documents with an ObjectId by default. To change this behavior, just turn it off:
var UserSchema = new Schema({
_id: Number
},{ "_id": false });
mongoose.model('User', UserSchema)
So the _id generation is disabled by this option, and this could cause an error in a "top level" schema unless of course you define the field yourself. When you do then the type given is respected and used.
Without the option, the default is used and overrides any declaration you used in the schema. So this code now works as expected:
user = new User({ "_id": 1 });
user.save(function(err,doc) {
if (err) throw err; // but wont fail to cast type now
console.log( doc );
});

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.

document must have an _id before saving mongoose error

I am trying to create a schema.
I keep getting the document does not have an _id error, besides the code below I did try to initialize it explicitly, but nothing works.
var UserSchema = new mongoose.Schema({
_id: mongoose.Schema.ObjectId,
username: String,
password: String
});
var User = mongoose.model('user', UserSchema);
http://mongoosejs.com/docs/guide.html#_id reads:
Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema constructor.
If you explicitly define _id type in the schema, it's your responsibility to set it:
User._id = mongoose.Types.ObjectId('000000000000000000000001');
_id is the primary key for document in a mongoDB. You don't have to specify the _id in your Schema. It will be added automatically once the document is created.
Here is the sample code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = new Schema({
username: {
type: String
},
password: {
type: String
}
});
module.exports = mongoose.model('User', User);
I think you dont need to define the _id. Try without and see if it works.
Also if that is not the problem try this:
_id: { type: Mongoose.Schema.Types.ObjectId }
if you want to define _id in your schema explicity you should assign a value to "_id" for each insertation. you have two way to solve this problem :
1. remove "_id" from your schema and mongoose generate id automatically.
2. assign a value to _id :
var ObjectId = require('mongodb').ObjectID; // or var ObjectId = require('mongoose').Types.ObjectId; "but the first worked for me"
User._id = objectId('1111111111111111111');
simple remove the line from your code
_id: mongoose.Schema.ObjectId

Mongoose elemMatch returns an empty array

I am currently building a web application and I have stumbled upon a problem I wasn't able to solve for the past 12 hours.
I am working on a basic get method where I am trying to retrieve certain work positions (i.e. cashier, clerk) that an employee is working as (the client-side works perfectly).
The mongoose model for WorkPosition is such (models/work-position.js):
var mongoose = require('mongoose');
var User = require('./user');
var Schema = mongoose.Schema;
var schema = new Schema ({
workplace : {type: String, required: true},
type : {type: String, required: true},
status : {type: String, required: true},
color : {type: String, required: true},
employees: [{type: Schema.Types.ObjectId, ref:'User'}]
});
module.exports = mongoose.model('WorkPosition', schema);
My get method (routes/work-position.js):
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var User = require('../models/user');
var mongoose = require('mongoose');
var WorkPosition = require('../models/work-position');
router.get('/:userId', function(req, res, next) {
const userId = req.params.userId;
WorkPosition.find({employees: {$elemMatch : {$eq: userId}}})
.exec(function(err, workPositions) {
if(err) {
return res.status(500).json({
title: 'an error has occurred',
error: err
});
}
console.log(userId);
console.log(workPositions);
res.status(200).json({
message: 'Success',
obj: workPositions
});
});
});
The problem arises when I try to use the $elemMatch method. The code above, when the WorkPosition.find line is changed to
WorkPosition.find()
without any conditions (
{employees: {$elemMatch : {$eq: userId}}}
) inside, I am successfully able to retrieve the WorkPosition document that I desire.
However, I want to only retrieve the WorkPosition documents where the 'employees' field in WorkPosition matches the 'userId' I have received from req.params. Therefore, I searched through the mongodb/mongoose API (http://mongoosejs.com/docs/api.html#query_Query-elemMatch)
where I found the $elemMatch method.
In the mongodb shell, when I do
db.workpositions.find({"employees": { $elemMatch : {$eq: "596823efbac11d1978ba2ee9"}}})
where "5968...." is the userId, I am successfully able to query the WorkPosition document.
Through this command, I am able to verify that my logic is correct, and using the mongodb native shell command gets me the document I desire.
However, when I try to convert the same logic to the Mongoose API, which is:
WorkPosition.find().elemMatch('employees', {$eq : userId})
I get an empty array, and adding lines
mongoose.set('debug', function (coll, method, query, doc) {
console.log(coll + " " + method + " " + JSON.stringify(query) + " " + JSON.stringify(doc));
});
in /app.js , I am able to see what the mongoose query translates to native mongodb command which is :
workpositions find {"employees":{"$elemMatch":{"$eq":"596823efbac11d1978ba2ee9"}}} {"fields":{}}
. The collection (workpositions), method (find), array to seek (employees) and everything is correctly translated to native mongodb command EXCEPT
"$eq"
. The only difference between the shell command that successfully works and the mongoose command in my code is the additional quotation marks around '$eq'.
shell:
db.workpositions.find({"employees": { $elemMatch : {$eq: "596823efbac11d1978ba2ee9"}}})
mongoose:
db.workpositions.find({"employees": { $elemMatch : {"$eq": "596823efbac11d1978ba2ee9"}}})
I cannot seem to find a way to get rid of these extra quotation marks (which I believe is the cause of the problem). I tried using the native command with mongoose like :
mongoose.connection.db.workpositions.find(.....)
but this also results in an error.
What is the proper way to use elemMatch through mongoose? Can anyone enlighten me how to use it? I tried every way I could think of, and I cannot seem to get past this problem.
I am thinking of changing the WorkPosition.employees field so that it holds actual Users (not only userIds), and I can parse through with additional information that exactly matches the Mongoose elemMatch API. However, I believe this will waste a HUGE amount of database space because each WorkPosition has to carry an array of Users.
To have the correct relationship between Users and WorkPositions, the elemMatch is a requirement in my project. I would greatly appreciate any help!
Please use mongoose.Types.ObjectId around the $eq array operator when comparing mongodb ObjectIds, then you can retrieve the first document that matches the condition with $elemMatch operator.
$elemMatch:{$eq:mongoose.Types.ObjectId(userId)}

Mongoose can't find any elements after changing property type

I originally have these two schemas:
var UserSchema = new Schema({
first: String,
last: String
});
var SaleSchema = new Schema({
createdAt: Date,
registeredBy: { type: Schema.ObjectId, ref: 'User' }
});
But I want to edit my SaleSchema to save the user name instead of the ID, so I changed it for:
var SaleSchema = new Schema({
createdAt: Date,
registeredBy: String
});
Next, I wanted to edit all the Sales documents and replace the user IDs on registeredBy for the user's full name, but I can't seem to be able to perform a find query for the old ID's.
Long story short, this query returns no matches on mongoose, but it works perfectly using the mongo console:
Mongoose
Sale.find({ registeredBy: '57ea0cbb47431f0b43b87d42' })
.then(results => res.json(results))
.catch(err => res.status(500).json(err));
// result: []
MongoDB console
db.sales.find({ registeredBy: '57ea0cbb47431f0b43b87d42' })
// result: 8 elements
After I modify my schema's property back to ObjectId, the mongoose query works again. Since I need to migrate to a new datatype, I want to be able to query and store both types of values. Is this possible?
Good question this is a complicated edge case. I am not super familiar with Mongoose specifically, but one way to do this would be to migrate your data at a lower level. For example, create a mongo script that uses the low-level mongo API to do the migration, something along the lines of:
db.sales.find().forEach(function(doc){
var user = db.users.find({ _id: doc.registeredBy });
db.sales.update({ _id: doc._id, }, {
$set: { registeredBy: user.first + ' ' + user.last }
});
});
This is similar to what a module like https://github.com/balmasi/migrate-mongoose does, but I've personally found it easier to use mongo scripts on the cli directly.
mongo < sale-schema-migration.js

Node, Mongoose: on save() "VersionError: No matching document found."

I'm new to mongoose so this is probably something very simple .. however.
I have a very simple schema that contains a simple array of numbers:
userSchema = new mongoose.Schema({
name : String,
tag_id : String,
badges : [Number]
});
var user = mongoose.model( 'User', userSchema );
Later I want to add a badge to the user. So..
user.findOne({tag_id:tagid}, function(err,doc) {
if (!doc) callback(false, 'no doc');
// check to see if badge is in array, if not add it
if ( doc.badges.indexOf(badgeNum) == -1 ) {
doc.badges.push(badgeNum);
doc.save( function(err) {
if (err) callback( false, err );
else callback( true, '');
});
} else {
callback( false, 'user already had badge' )
}
});
However whenever I run that code I get a 'VersionError: No matching document found.'
I did a little googling and found reference to versioning being added in 3.x mongoose and that it's generally all handled internally. It should be noted that I loaded this user data straight into mongo via json and the mongo commandline ( so it wouldn't have the versioning variable defined by default, but I suspect that mongoose would create it if it didn't find it... bad assumption?)
EDIT: Sorry my simplified example had an error in it.
I solved this problem by adding versioning fields to my imported dataset and modifying the schema to associate it with versionKey.
http://mongoosejs.com/docs/guide.html#versionKey
I do wonder, however if there isn't a setting that lets mongoose add the default versionkey (__v) to data that lacks it.
You are trying to assign the testSchema to the user object instead of the userSchema so it is looking for an document type that hasn't been created.
Update the user assignment to the following:
var user = mongoose.model('User', userSchema);
I would suggest you follow the guides if you aren't sure what you're doing.

Resources