Mongoose.populate() producing no change in the model - mongodb

Listing Schema:
const mongoose = require('mongoose');
const listingSchema = new mongoose.Schema({
title: String,
name: String,
tel: String,
service: String,
description: String,
location: Object,
isAvailible: Boolean,
canTravel: Boolean,
distance: Number,
isPublic: { type: Boolean, default: true},
pro: { type: mongoose.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
const Listing = mongoose.model('Listing', listingSchema);
module.exports = Listing;
Request to DB:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
await listings[0].populate('pro');
console.log(listings[0].pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});
Screenshot of console
I'm also curious what is the best way to populate an array of models, however, I can't even get it to populate one. Any thoughts?

can you please tye execPopulate() method
try below code
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listing=await listings[0].populate('pro').execPopulate();
console.log(listing.pro);
res.render('search', {
title: 'Search',
listings: listing,
search: {
service: service,
zip: zip
}
});
});

assignment to constant variable before the populate may work like so:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listings = await listings[0].populate('pro');
console.log(listings.pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});

Related

How to update a user profile which has a property which is a ref in MongooseJS?

I have a User schema which has reference to a profile schema.
const UserSchema = new Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
....email props...
},
password: {
...password props...
},
profile: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Profile",
}],
},
);
const Profile = new Schema({
_user: {
type: Schema.Types.ObjectId, ref: 'User'
},
'displayName': {
type: String,
default: ''
},
'interestedActivities': ['Ping-pong'], <---- This bad boy/girl is an array
'memberSince': { type: Date, default: Date.now }
}
)
I'd like to create a route which can update the User properties AND the Profile properties in one shot—with a caveat one of the properties on the Profile model is an array!!!
I tried this....
handler
.use(auth)
.put((req, res, next) => {
emailValidator(req, res, next, 'email');
},
async (req, res, next) => {
await connectDB()
const {
profileDisplayName,
profileEmail,
interestedActivities } = req.body;
const update = {
email: profileEmail,
'profile.$.displayName': profileDisplayName,
'profile.$.interestedActivities': interestedActivities
}
const filter = { _id: req.user.id };
const updatedUser = await User.findOneAndUpdate(filter, update, { new: true })
try {
console.log("updatedUser ", updatedUser);
if (updatedUser) {
return res.status(200).send({
updatedUser,
msg: `You have updated your profile, good job!`
});
}
} catch (error) {
errorHandler(error, res)
}
})
export default handler;
My response is:
Status Code: 500 Internal Server Error
Cast to ObjectId failed for value "[
{
id: 'ae925393-0935-45da-93cb-7db509aedf20',
name: 'interestedActivities',
value: []
}
]" (type Array) at path "profile.$"
Does anyone know how I could also afford for the property which is an array?
Thank you in advance!

Mongoose - Update/Find Specific Object in an Array Not Working As Expected

I am following the docs without luck and am at a standstill while trying to update an object in an object in an array using MongoDB and Mongoose.
Here is my document:
{
fields: [
{ id: 603d63086db2db00ab09f50f, data: [Object] },
{ id: 603d63086db2db00ab09f510, data: [Object] },
{ id: 603d63086db2db00ab09f511, data: [Object] },
{ id: 603d63086db2db00ab09f512, data: [Object] },
{ id: 603d63086db2db00ab09f513, data: [Object] },
{ id: 603d63086db2db00ab09f514, data: [Object] },
{ id: 603d63086db2db00ab09f515, data: [Object] }
],
layouts: [],
_id: 603d631a6db2db00ab09f517,
bandId: '603d63146db2db00ab09f516',
eventType: 'private',
ownerId: '6039354906410800c14934c1',
__v: 0
}
I am trying to updateOne of the fields.data in the fields array. fields.data is an object as well.
I call my Express/Node Backend to this route.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.updateOne(
{ _id: req.params.id, "fields.id": req.body.id },
{
"$set": {
"fields.$.data": req.body.data,
},
}
);
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
Where req.body is:
{ id: '603d63086db2db00ab09f50f', data: { type: 1, rate: '200.30' } }
**Just in case it's helpful, here is what one of the fields objects looks like in the document,
{"id":"603d63086db2db00ab09f50f","data":{"type":1,"rate":300}}
I have even tried changing my route to find this document - which I have confirmed exists - Truly at a loss why it won't find the document.
Here is how I changed the above route to find the document.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.find(
{ _id: req.params.id, "fields.id": req.body.id },
);
console.log(updating) //returns []
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
The Model
//Create Schema - QG
const QuoteGeneratorSchema = new Schema({
bandId: {
type: String,
required: true,
},
ownerId: {
type: String,
required: true,
},
fields: {
type: Array,
default: defaultFields,
required: true,
},
eventType: {
type: String,
required: false,
},
layouts: {
type: Array,
required: false,
},
});
let QuoteGenerator = mongoose.model("QuoteGenerator", QuoteGeneratorSchema);
module.exports = QuoteGenerator;
Any nudge in the right direction to replacing that data object with a new data object would be extremely helpful! Thanks!

Mongoose text index search returns empty array

I'm trying to query indexes, but I receive an empty array. I can't find what's wrong with my code. I used two methods to create the index: 1) VideoSchema.index() and 2) in the schema itself, both of them don't work. I checked the mongodb and it seems that indexes are created correctly, so I don't know what I do wrong.
const mongoose = require("mongoose");
const VideoSchema = mongoose.Schema(
{
user: {
type: mongoose.ObjectId,
required: true,
ref: "user",
},
title: {
type: String,
maxLength: 100,
text: true,
},
description: {
type: String,
text: true,
},
publishDate: {
type: Date,
},
views: {
type: Number,
default: 0,
},
likes: {
type: Number,
default: 0,
},
dislikes: {
type: Number,
default: 0,
},
comments: [
{
type: mongoose.ObjectId,
ref: "comment",
},
],
urls: {
video_url: {
type: String,
required: true,
},
thumbnail_url: {
type: String,
},
preview_url: {
type: String,
required: true,
},
},
private: {
type: Boolean,
default: 0,
},
category: {
type: String,
default: "",
},
duration: {
type: Number,
required: true,
},
},
{ timestamps: true }
);
// VideoSchema.index({ title: "text", description: "text" });
// export model user with UserSchema
module.exports = mongoose.model("video", VideoSchema);
The query:
const express = require("express");
const router = express.Router();
const Video = require("../model/Video");
router.post("/", (req, res) => {
const query = req.body.query;
Video.find({ $text: { $search: query } }, { score: { $meta: "textScore" } })
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
module.exports = router;
As you are fetching data from your Database it´s a good practice and makes the code clearer if you use the 'GET' method. If you do so, there is no need to add the score option to the query since V.4.4
const express = require("express");
const router = express.Router();
const Video = require("../model/Video");
router.get("/", (req, res) => {
const query = req.query.YOUR_QUERY_PARAMETER;
Video.find({ $text: { $search: query }})
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
module.exports = router;
If the problem persists:
Try to add the wild card text indexing to see if the problem is within it as follows:
VideoSchema.index({'$**': 'text'});
If so, then drop the collection for a fresh start on the indexing and then append your text indexes like this:
VideoSchema.index({ title: "text", description: "text" });
Create new dummy items and then check again.
Make sure you read the exceptions shown in the MongoDB documentation:
https://docs.mongodb.com/manual/reference/operator/query/text/
It seems that I resolved the problem. I noticed that in the express js the 'query' keyword is used for 'get' request params, so I decided to change this variable to 'search', so now it is like underneath and it is working!
router.get("/", (req, res) => {
const { search } = req.query;
Video.find(
{ $text: { $search: search } },
{ score: { $meta: "textScore" } }
)
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
But I've noticed that I'm getting only one video instead of two that contains the 'obs' in the title, so now I will need to deal with that.
Thank you so much for your time and effort!

make a path that increments the count

I'm trying to make a post request that will increment my schema using express and mongoose,
which is :
const ItemSchema = new Schema({
formName: String,
inputs: [
{
inputLabel: {
type: String,
required: true
},
inputType: {
type: String,
required: true,
enum: ['text', 'color', 'date', 'email', 'tel', 'number']
},
inputValue: {
type: String,
required: true
}
}
],
numOfSubs: { type: Number, default: 0 }
});
for my code purposes I want to make a route that will increase by 1 the numOfSubs everytime I use it,since there are a few listings, I have the ID so I need to search it, and I'm not sure how to write the path
router.post('/increase', (req, res) => {
"find and increase by 1 "
});
and I will use the fetch like so:
fetch('/api/items/increase', {
method: 'POST',
body: JSON.stringify({ _id }),//the ID I of the collection I want to increment
headers: {
'content-type': 'application/json'
}
});
try this using mongo $inc operator
router.post('/increase', (req, res, next) => {
const _id = req.body._id;
MyModel.findByIdAndUpdate(_id , { $inc: {numOfSubs: 1} }, { new: true }, (err,updateRes)=>{
if(err) return next(err);
return res.json({sucess: true});
});
});

Multiple chained promises is sailsjs

This is my first attempt at attempting to chain multiple finds together. The debug running shows that all the code executes correctly but there is a delay in receiving the users array back and therefore unable to present the data back.
The concept is a user may belong to multiple organizations, and there may be more than one user (other than the current user) that may belong to organizations. The function is trying to receive all users for all the organizations the current user belongs to.
getUserOrganizationsUsers: function (userId) {
var users = [];
sails.log.info('Getting the current users organizations [' + userId + ']');
return UserOrganization.find({ user_id: userId, deleted: null })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (userorganization) {
return userorganization;
})
.then(function (userorgs) {
/* From all the organizations I want to get all the users from those organizations */
_.forEach(userorgs, function (userorg) {
UserOrganization.find({ organization_id: userorg.organization_id.id })
.populate('organization_id', { deleted: null })
.populate('user_id', { deleted: null })
.then(function (otherusrs) {
_.forEach(otherusrs, function (otherusr) {
sails.log.info('other userss each loop ');
var users = _.find(otherusrs, {id: otherusr.organization_id.id});
users.push(users);
})
})
});
return Q.when(employees);
})
},
Organization.js
module.exports = {
attributes: {
companyName: {
type: 'string',
required: true
},
Address: {
type: 'string'
},
ABN: {
type: 'string'
},
City: {
type: 'string'
},
contactNumber: {
type: 'string'
},
country: {
type: 'string'
},
icon: {
type: 'string'
},
users:
{ collection: 'userorganization',
via : 'user_id'
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, Organization.publicFields);
return obj;
}
},
editableFields: [
'companyName',
'users'
// 'industries'
],
publicFields: [
'id',
'companyName',
'users'
],
};
UserOrganization.js
module.exports = {
attributes: {
organization_id: {
model : 'organization',
required: true
},
user_id: {
model: 'user',
required: true
},
organizationRole: {
type: 'string',
required: true
},
deleted: {
type: 'date',
defaultsTo: null
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, UserOrganization.publicFields);
return obj;
}
},
editableFields: [
'organization_id',
'user_id',
'organizationRole',
],
publicFields: [
'id',
'organization_id',
'user_id',
'organizationRole'
],
};
and the user.js
var bcrypt = require('bcrypt-nodejs');
module.exports = {
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
required: true
},
firstName: {
type: 'string'
},
lastName: {
type: 'string'
},
verified: {
type: 'boolean',
defaultsTo: false
},
organizations:
{ collection: 'userorganization',
via : 'user_id'
}, deleted: {
type: 'date',
defaultsTo: null
},
fullName: function () {
return this.firstName + ' ' + this.lastName;
},
toJSON: function () {
var obj = this.toObject();
obj = _.pick(obj, User.publicFields);
return obj;
}
},
// TODO: Add initialFields
editableFields: [
'password',
'email',
'firstName',
'lastName',
'organizations'],
publicFields: [
'id',
'email',
'verified',
'firstName',
'lastName',
'fullName',
'organizations'
],
comparePassword: function (password, user, cb) {
bcrypt.compare(password, user.password, function (err, match) {
if(err) return cb(err);
cb(null, match);
})
},
beforeCreate: function (user, cb) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(user.password, salt, function () {}, function (err, hash) {
if (err) {
sails.log.error(err);
return cb(err);
}
user.password = hash;
cb(null, user);
});
});
}
};
Okay, I think I understand what you're doing. It would be a lot simpler to have the User belong to an organization directly.
Anyways, if I understood your model structure correctly, something like this should work:
getUserOrganizationsUsers: function (userId) {
UserOrganization.find({ user_id: userId, deleted: null })
.then(function (userOrgs) {
// return array of organization IDs
return _.map(userOrgs, function(org){
return org.id;
});
})
.then(function (userOrgs) {
Organization.find(userOrgs)
.populate('users') // users is a collection of UserOrganization
.exec(function(err, orgs){ // lookup organizations
if(err) //handle error
else {
return _.flatten( // return basic array for next promise handler
_.map(orgs, function(org){ // for each organization
return _.map(org.users, function(user){ // return an array of user_ids
return user.user_id;
})
})
)
}
})
})
.then(function(allUserOrgs){
UserOrganization.find(allUserOrgs)
.populate('user_id')
.exec(function(err, userOrgsList){
return _.map(userOrgsList, function(user){
return user.user_id;
})
})
})
.then(function(users){
// users should be an array of all the users form allt he organizations that the current users belongs to
})
},