Mongoose elemMatch returns an empty array - node.js

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)}

Related

Cast error while executing a mongoose query

I am executing a query in mongoose where I need to find all the users in my database and sort them and limit the results to 10.
My query route is:(the route is "/user/top")
router.get('/top', middleware.ensureAuthenticated, function (req, res) {
User.find({}).sort({bestScore: 1}).limit(10).exec(function (err, result) {
if(err){
res.json({
status:"error",
data:err
});
}
else {
res.json({
status:"ok",
data: result
})
}
})
});
My User model:
var UserSchema = new mongoose.Schema({
image: String,
displayName: String,
bestScore:Number,
});
The error while i call the url from postman
EDIT 1:
Output of mongodb query:
You can see that my _id is of type ObjectId .
Your data probably contains a document which looks like this:
{
_id: "top",
...
}
Since Mongoose expects that value to be an ObjectId by default you get this error. What you can do is map the _id field explicitly as a string:
var UserSchema = new mongoose.Schema({
_id: String,
image: String,
displayName: String,
bestScore:Number,
});
The issue you had is that your search route is going through findbyId. if you truly wanna search/find all users without using an Id, you need to make sure in your controller, your search comes before your getbyId. same also in your route. I had the same error here and I was able to solve it from my route by putting my search function before getbyId.
Also, when you are using postman, kindly enter the search parameter as seen in my own case study below;
take note the path and my parameter key and value:

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

Mongoose Saving _id's as Numbers

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 );
});

MongoDB find() returns nothing

I am trying to query my database from the mongodb shell to retrieve all the entries. Still, my find() method returns nothing.
This is the mongoose model that I am using:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ArticleSchema = new Schema({
title: String,
content: String,
author: { type: String, default: 'Vlad'},
postDate: { type: Date, default: Date.now }
});
ArticleSchema.methods.formatTitle = function() {
var link = this.title.replace(/\s/g, '-');
return '/article/' + link;
};
ArticleSchema.methods.snapshot = function() {
var snapshot = this.content.substring(0, 500);
return snapshot;
};
module.exports = mongoose.model('Article', ArticleSchema);
When loading my Express.js application in the browser, I have no issues displaying all the articles that I have added using my API. Still, I just want to go inside the mongo shell and query them. I have used the following query:
// blog is the database name (mongodb://localhost:27017/blog)
db.blog.find()
I get an empty string in return, basically a new line in the console.
Your answer is right there, in the question:
// blog is the database name (mongodb://localhost:27017/blog)
db.blog.find()
So db already refers to correct database. Instead of blog, put collection name, not db name.
db.articles.find()
Mongo shell will connect when started to the test database, you will need to change the database and then execute the find on your collection :
use blog
db.articles.find()
First you choose the db, then you find on the collection.
use blog
db.articles.find()

How to perform findOne query in mongoose

i have mongoose schema named administrator
var administratorSchema = new mongoose.Schema({
username : String,
password : String,
active : Boolean,
level : String
});
When i try this query,i can get the result
mongoose.connect('mongodb://'+dbServer+'/'+dbName, function(connectionError) {
var administratorModel = mongoose.model('administrators',administratorSchema);
administratorModel.findOne({_id,111155dffxv}function(err, resad){
console.log('into mongoose findone');
});
});
====> Console output : 'into mongoose findone'
The problem is : when i try to change the criteria from _id to "username", mongoose dosen't work and findOne dosen't execute:
mongoose.connect('mongodb://'+dbServer+'/'+dbName, function(connectionError) {
var administratorModel = mongoose.model('administrators',administratorSchema);
administratorModel.findOne({'username','mohamed'}function(err, resad){
console.log('into mongoose findone');
});
});
====> Console output : ''
Thanks.
Your query object isn't valid (use a colon instead of a comma) and you're missing a comma between the findOne parameters. Your call should look like this instead:
administratorModel.findOne({'username': 'mohamed'}, function(err, resad){
console.log('into mongoose findone');
});
You should also be checking the err parameter of your callbacks to see if things are working.
Not sure why it was reaching the callback with your _id criteria version as that one has the same issues.

Resources