Populate or not based on a condition mongodb using mongoose - node.js

I have a following schema in mongoose,
Schema = new Schema({
category = { type: Schema.Types.ObjectId, ref: 'Category' },
subCategory = { type: Schema.Types.ObjectId, ref: 'subCategory' },
subSubCategory = { type: Schema.Types.ObjectId, ref: 'subSubCategory' },
name: String
});
Now I want to conditionally populate or not category, subCategory, subSubCategory based on a few parameters passed to the controller through req.query
Schema.find(function(err, data) {
if(err) { //handle errors }
if(!data) { //throw 404 }
res.status(200).json(data);
})
.populate('category') //execute only if(req.query.populateCategory == true)
.populate('subCategory') //execute only if(req.query.populateSubCategory == true)
.populate('subSubCategory'); //execute only if(req.query.populateSubSubCategory == true)
How can that be achieved?

Mongoose model find function returns Query instance, which you can use to pipe new functions:
When a callback function is passed, the operation will be executed immediately with the results passed to the callback. When it is not passed, an instance of Query is returned, which provides a special query builder interface.
var query = Schema.find({}); // TODO: add filter
if (req.query.populateCategory == true) {
query = query.populate('category');
}
if (req.query.populateSubCategory == true) {
query = query.populate('subCategory');
}
if (req.query.populateSubSubCategory == true) {
query = query.populate('subSubCategory');
}
query.exec(function(err, data) {
if (err) { //handle errors }
if (!data) { //throw 404 }
res.status(200).json(data);
});

Related

Sequelize how to get all data if there is no query string passed

I'm pretty new to Sequelize.
I'm trying to create a handler to get all playlists in my database.
This is what I want to do:
If there is a query string then it should return the result based on that query.
If there is no query string passed then it should return all my playlists.
This is my playlist model:
const Playlist = db.define("playlist", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
unique: true,
},
});
Here is my handler:
exports.getPlaylists = async (req, res) => {
try {
const { name } = req.query;
console.log(name); // this prints the name
const result = await Playlist.findAll({
where: {
name:
name === undefined ? {} : { $like: `%${name}%` },
},
});
if (result) {
res.status(200).send(result);
} else {
response.status(404).send("No Playlists found");
}
} catch (error) {
res.status(500).send(`Internal server error: ${error}`);
}
};
This works well if I passed a name in the query. but If I didn't pass any query string. It returns an empty array.
$like is an alias for Sequelize.Op.like
What should I put instead of the empty object?
I checked this question How get all data if field in query string is empty Node and Sequelize with Postgres but the proposed solutions didn't work with me
Create a filter object based on the condition. If you pass empty object to where, it won't apply that in the query.
exports.getPlaylists = async (req, res) => {
try {
const { name } = req.query;
const filters = {};
if (name)
filters.name = {
[Op.like]: `${name}%`, // you can also use $like if you are using older version of sequelize
}
const result = await Playlist.findAll({
where: filters,
});
if (result) {
res.status(200).send(result);
} else {
response.status(404).send("No Playlists found");
}
} catch (error) {
res.status(500).send(`Internal server error: ${error}`);
}
};
This way you can prepare complex filters based on other query strings.

Mongoose is only returning ID from MongoDB

I am currently trying to incorporate datatables with my MongoDB database. I am having some trouble accessing the returned object though. The main problem I am seeing is that I am only getting the _id returned from MongoDB, and no values of the object.
Heres the code I am using to pass the information to the datatables.
var itemsModel = require('./models/itemReturn');
exports.getItemList = function(req, res) {
var searchStr = req.body.search.value;
if (req.body.search.value) {
var regex = new RegExp(req.body.search.value, "i")
searchStr = { $or: [{ 'productName': regex }, { 'itemPrice': regex }, { 'Quantity': regex }, { 'Description': regex }, { 'seller': regex }] };
} else {
searchStr = {};
}
var recordsTotal = 0;
var recordsFiltered = 0;
itemsModel.count({}, function(err, c) {
recordsTotal = c;
console.log(c);
itemsModel.count(searchStr, function(err, c) {
recordsFiltered = c;
itemsModel.find(searchStr, 'productName itemPrice Quantity Description seller', { 'skip': Number(req.body.start), 'limit': Number(req.body.length) }, function(err, results) {
if (err) {
console.log('error while getting results' + err);
return;
}
var data = JSON.stringify({
"draw": req.body.draw,
"recordsFiltered": recordsFiltered,
"recordsTotal": recordsTotal,
"data": results
});
console.log(data);
res.send(data);
});
});
});
};
This is the model
// app/models/itemsReturn.js
// load the things we need
var mongoose = require('mongoose');
var schemaOptions = {
timestamps: true,
toJSON: {
virtuals: true
},
toObject: {
virtuals: true
}
};
// define the schema for our item model
var itemsReturned = mongoose.Schema({
productName: String,
itemPrice: String,
Quantity: String,
Description: String,
seller: String
}, schemaOptions);
// create the model for users and expose it to our app
var items = mongoose.model('items', itemsReturned);
module.exports = items;
The thing is that I know its not a data table issue as I can make the _id appear in the tables. I just need to know how to return the entire object instead of just the _ID so that I can access the values of the object.
If it helps this is the tutorial I am following.
UPDATE: Okay so I figured out why my MongoDB collections were only returning the item ID. The issue was that I had stored everything in the local database (oops).

How to convert multiple Mongoose documents?

Each of my schemas have a method, called toItem() which converts the doc to a more verbose / human-readable form. How can I create a toItems() method to do the same thing for an array of documents?
My example schema:
var mongoose = require('mongoose');
var membershipSchema = new mongoose.Schema({
m : { type: mongoose.Schema.ObjectId, ref: 'member' },
b : { type: Date, required: true },
e : { type: Date },
a : { type: Boolean, required: true }
});
var accountSchema = new mongoose.Schema({
n : { type: String, trim: true },
m : [ membershipSchema ]
});
accountSchema.methods.toItem = function (callback) {
var item = {
id : this._id.toString(),
name : this.n,
members : []
};
(this.m || []).forEach(function(obj){
item.members.push({
id : obj.m.toString(),
dateBegin : obj.b,
dateEnd : obj.e,
isAdmin : obj.a
});
});
return callback(null, item);
};
var accountModel = mongoose.model('account', accountSchema);
module.exports = accountModel;
I've tried using statics, methods, and third-party libraries, but nothing clean works. I would like to keep this as simple / clean as possible and have the toItems() function contained within my model file.
Thank you, in advance.
Your toItem() method is specific to the schema / model. Your toItems() method sounds more like a utility method which can / will be used by all of your models. If so, I would move create the toItems() method inside a utility file. You would simply pass in the array of documents and the utility method would call the individual toItem() method on each document.
For example:
var async = require('async');
var toItems = function (models, callback) {
models = models || [];
if (models.length < 1) { return callback(); }
var count = -1,
items = [],
errors = [];
async.forEach(models, function (model, next) {
count++;
model.toItem(function (err, item) {
if (err) {
errors.push(new Error('Error on item #' + count + ': ' + err.message));
}
else {
items.push(item);
}
next();
});
}, function (err) {
if (err) {
return callback(err);
}
if (errors.length > 0) {
return callback(errors[0]);
}
return callback(null, items);
});
};
module.exports.toItems = toItems;

Mongoose cascading deletes in same model

This is different than this and this. But they are very helpful.
Basically, I have a Topic schema. If one Topic get's deleted, I want to delete other topics. Think of a graph where deleting a node means deleting the edges.
var schema = new Schema({
title: { type: String, required: true, trim: true },
srcId: { type: Schema.Types.ObjectId, validate: [edgeValidator, 'Set both srcId and destId, or neither'] },
destId: Schema.Types.ObjectId,
});
I want the 2nd mongo delete to run in the schema.pre('remove', ...)
But I don't have a model at this point. So calling .find() or .remove() doesn't work. What's the best way?
schema.pre('remove', function(next) {
var query = '';
var self = this;
if (this.isEdge) {
query = 'MATCH ()-[r:CONNECTION { mongoId: {_id} }]-() DELETE r;';
} else {
query = 'MATCH (n:Topic { mongoId: {_id} })-[r]-() DELETE n,r;';
}
// This is another database.
neo.query(query, this, function(err, data) {
if (err) return next(err);
if (self.isEdge) {
return next();
} else {
// Now we're back to mongoose and mongodb
// Find and remove edges from mongo
schema.find({ mongoId: { // <------ .find() is undefined
$or: [
{ srcId: self._id },
{ destId: self._id }
]
}}, function(err, edges) {
edges.remove(next);
});
}
});
});
This turned out to be pretty easy.
var Model = null;
var schema = ...
module.exports = Model = mongoose.model('Topic', schema);
Then just use Model in the pre-remove. Piece of pie.

Mongoose model get undefined properties after population

I got a problem for a basic request.
All properties of a mongoose model I fetch are undefined in the exec() callback.
Here is my schema :
userSchema: new Schema({
email: { type: String, limit: 50, index: true },
password: String,
birthdate: { type: Date },
active: { type: Boolean, default: true },
friends: [{
_friend: { type: Schema.ObjectId, ref: 'User' },
addedDate: { type: Date, default: Date.now }
}],
registrationDate: { type: Date, default: Date.now }
})
You can already notice that my "friends" property is an array of objects referencing another schema.
Now here is my query :
dbModels.User
.find({ _id: req.session.user._id })
.populate('friends._friend', 'email birthdate')
.exec(function (err, _user){
if (err || !_user){
apiUtils.errorResponse(res, sw, 'Error when fetching friends.', 500);
} else {
console.log('user', _user);
// This output the object with all its properties
console.log('user birthdate', _user.birthdate);
// _user.birthdate is undefined
console.log('user friends', _user.friends);
// _user.friends is undefined
apiUtils.jsonResponse(res, sw, _user);
}
});
When this web service return '_user', each properties are well defined and have the correct values.
The problem is that I only want to return _user.friends which is not possible since it's undefined.
Now, here is apiUtils.jsonResponse function :
exports.jsonResponse = function (res, sw, body) {
console.log(body.friends);
// At this breakpoint, body.friends is still undefined
(sw || _sw).setHeaders(res);
if (util.isArray(body)) {
for (var i = 0; i < body.length; i++) {
body[i] = exports.cleanResults(body[i]);
}
} else {
console.log(body.friends);
// At this breakpoint body.friends is still undefined
body = exports.cleanResults(body);
}
res.send(httpCode || 200, JSON.stringify(body));
};
And the cleanResults function :
exports.cleanResults = function (body) {
console.log(body.friends);
// At this point, body.friends is FINALLY DEFINED
if (typeof body.toObject === 'function') {
body = body.toObject();
delete body.__v;
}
for (var attr in body) {
if (body.hasOwnProperty(attr) && attr[0] == '_') {
var _attr = attr.replace('_', '');
body[_attr] = body[attr];
delete body[attr];
}
}
return body;
};
I tried to set a timeout to see if the problem came from async but it changed nothing. I'm a bit desesperate at this time and I wanted to know if you already encountered the same problem before ?
I see your problem, you have accidentally used find when you expect only one object to be returned. In this case, you should use findById:
User
.findById(req.session.user._id)
.populate('friends._friend', 'name surname picture birthdate')
.exec(function(err, user) {
...
})

Resources