In below my code, I try to append the populate in mainQuery object. If I pass only types, then I get the expected result and query also build. If I pass both types and sub category, then query was broken and object is not added as per expected query bellow. Kindly give solution for this ?
mainQuery = Category.find();
if(req.param('types')) {
searchKey = "types";
typesObj.where = {
id: 1
};
mainQuery.populate(searchKey, typesObj);
}
if(req.param('subcat')) {
searchkeySubCat = "subcategory";
typesSubcat.where = {
id: 1
};
mainQuery.populate(searchkeySubCat, typesSubcat);
}
mainQuery.exec(function (err, category) {
});
Expected Query as below
Category.find().populate('types', {where: {id:1}}).populate('subcategory', {where: {id:1}}).exec(function (err, res) {
console.log(res)
})
Try as below:
function _getWhereClause(key, value) {
return {where: {key: value}};
}
if (req.param('types')) {
populateKey = 'types';
mainQuery.populate(populateKey, _getWhereClause('id', 1));
}
// Do the same for any number of populate you want
At last execute the query:
mainQuery.exec(function (err, category) {
});
Check the documentation for more understanding- http://sailsjs.org/documentation/reference/waterline-orm/queries/populate
What are you tryin to is basically method chaining with mainQuery object . So basically you are forming one query and later on use that query . I will suggest that you make this query as a string .
for eg.
mainQuery = "Category.find().";
mainQuery+= "populate("+searchKey+","+ typesObj+").";
mainQuery +="populate("+searchkeySubCat+","+ typesSubcat+").";
mainQuery +="exec(function (err, res) { console.log(res)});";
So when you will access mainQuery you will have :
Category.find().populate('types', {where: {id:1}}).populate('subcategory', {where: {id:1}}).exec(function (err, res) {
console.log(res)
});
Related
Model.findOne() is returning null even if the valid collection is present in the respective Model
app.post("/fleetManagement", (req, res) => {
const requestedDriverID = req.body.driverId;
console.log(requestedDriverID);
Driver.findOne({
_id: requestedDriverID
}, function(err, requestedDriverResult) {
console.log(requestedDriverResult);
res.render("fleetManagement", {
reqDriver: requestedDriverResult
});
});
})
Output
Collection in Driver Model
Check out the Output and Collection of Driver Model
Try convert requestedDriverID to ObjectId(requestedDriverID)
You need to convert the _id input to ObjectId.
Here's the updated code for your reference:
app.post("/fleetManagement",(req, res)=>{
const requestedDriverID = req.body.driverId;
console.log( requestedDriverID);
Driver.findOne({_id: ObjectId(requestedDriverID) }, function(err, requestedDriverResult){
console.log(requestedDriverResult);
res.render("fleetManagement", {
reqDriver:requestedDriverResult
});
});
})
I've been trying to build a helper function that will allow me to apply DRY pattern in order to stop repeating myself. My backend and the whole application is complete, however, I'd like to optimize my code, and my actual problem is that pretty much every express http method is similarly formatted the same way. The only time I've happened to come close to the solution is when I've omitted the req.params as arguments. The problem is that each method has its own req.params format. Here's how I was trying to solve:
I tried to use node-persist package to store req.params, and it only works after I change and resave the file, which makes sense. This happened as I first passed the params as the argument and tried to pass the persisted params value when I call the function. If there's a way to have the req.params stored somewhere locally first, I wouldn't be worried.
Second Option, I tried to use recursion and called the so-called function twice. I expected the first call to return an undefined params, and the second function call to return stored req.params, but unfortunately it wasn't the case.
I then decided to try using req.redirect where I've to access the database with req.params that came from req.query. Although this works, it still brings me back to the same problem as I'll keep redirecting everything
My problem, I want to have a helper function like the following:
export const storage = require('node-persist'); //package to persist data
Few of types used:
type AllHTTPMethods = "post" | "delete" | "all" | "get" | "put" | "patch" | "options" | "head";
type HTTPMethod = core.IRouterMatcher<core.Express, AllHTTPMethods>;
export async function onFetch(httpMethod: HTTPMethod | any, sql: string, path:string, params?: string){
httpMethod(path, async(req, res) => {
await storage.init({});
/**
Check if there is something already stored
*/
if (Object.keys(req.params).length === 0) {
await storage.setItem('params', req.params)
await storage.updateItem('params', req.params)
}
conn.query(sql, [params],
(err:any, data:any) => {
if (err) {
return new Error("Something went wrong\n"+err)
}
console.log("Successfully fetched");
console.log(data)
return res.json(data);
})
})
}
Here's how I invoked them:
//This one works because params aren't involved
async all() {
await onFetch(app.get.bind(app), "select * from products", "/products")
.then(() => console.log("Successfully fetched products"))
.catch(e => console.error(e))
}
//This one didn't work Because the function needs to called first before
//persisting
getProductById = async () => {
await storage.init()
const paramsObj = await storage.getItem("params"); //returns empty object {}
await onFetch(app.post.bind(app), "select * from products where id = ?", "/product/:id", paramsObj.id)
}
And the last trick I tried was to have req.params from client upfront, then redirect them to another router
Helper function to send req.params:
export function generateParams(
httpMethod: HTTPMethod,
path: string,
params: string,
) {
httpMethod(path, (req, res) => {
const paramsObject = JSON.stringify(req.params);
return res.redirect(`/params/api/?${params}=${paramsObject}`)
})
}
Calling:
generateParams(app.post.bind(app), "/product/:id", "product")
It works but it's still the same problem I was trying to avoid beforehand
app.get('/params/api', async (req, res)=> {
var product: string | string[] | any | undefined = req.query.product;
var id:any = JSON.parse(product).id
conn.query("select * from products where id = ?", [id], (err, data)=>{
if (err) {
return
}
res.json(data)
})
});
Thanks in advance
I created a helper function to handle the queries and params based on the current req.route.path, then return an array of string containing those queries
function setRequestData(request: any) {
var arr: any[] = [];
const query = request.query
const path = request.route.path
if (path === '/product/:id') {
arr = [...arr, request.params.id]
}
else if (path === '/add/user') {
arr = [...arr,
query.firstName,
query.lastName,
query.email,
query.phoneNumber,
query.passCode,
query.age,
]
}
console.log(arr)
return arr
}
Then I used it as the following:
export function onFetch(httpMethod: HTTPMethod, sql: string | mysql.QueryOptions, path:string){
try {
httpMethod(path, (req, res) => {
var queries:any[] = setRequestData(req)
conn.query(sql, queries, (err:any, data:any) => {
if (err) {
return new Error("Something went wrong\n"+err)
}
console.log("Successful request");
res.json(data);
})
})
} catch (error) {
console.error(error)
}
}
Now, I'd be just calling one line of code to communicate with mysql database no matter which method I intend to use:
var sql = `insert into Users(firstName, lastName, email, phoneNumber, passCode, age, joined) values(?, ?, ?, ?, ?, ?, current_timestamp())`;
onFetch(app.post.bind(app), sql, '/add/user')
i can not able to update nested data in my mongodb. here is my "update" module at back-end side.
exports.updateOne = (req, res) => {
if (!req.body) {
return res.status(400).send({
message: "Data to update can not be empty!"
});
}
const {id} = req.params;
console.log(req.body);
User.findByIdAndUpdate(id, req.body, { useFindAndModify: false, new: true}).populate('basic')
.then(data => {
if (!data) {
res.status(404).send({
message: `Cannot update User with id=${id}. Maybe User was not found!`
});
} else
res.send({ message: "User was dupdated successfully." , data});
})
.catch(err => {
res.status(500).send({
message:
err.message || "Error updating User with id=" + id
});
});
};
and my front-end side is;
onChangePosition(e) {
const position = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
basic:
{
...prevState.currentStaff.basic,
position:position
}
}
}));
}
onChangeEmail(e) {
const emailBusiness = e.target.value;
this.setState(prevState => ({
currentStaff: {
...prevState.currentStaff,
emailBusiness:emailBusiness
}
}));
}
updateStaff() {
StaffDataService.updateOne(
this.state.currentStaff.id,
this.state.currentStaff
).then(response => {
console.log(response.data);
})
.catch(e => {
console.log(e);
})
}
i can change state properly, and my sending data "req.body" is what i want (it is an object). There is no problem.
as you see above, i can update "email" because it is on the main body of object, but can not update "position" (nested element) because it is inside of basic (populated data).
i tried different methods by mongoose, and tried "$set" command.
Can anyone solve this?
To update, the nested value/object in your document, you should use dot notations, so it depends from the req.body variable value.
req.body shouldn't be a Mongoose doc. In such case you mongoose.toObject.
Second thing is:
[update] Object should be: field_with_subdocument.key_value: updated_propery
like this:
/** Example doc */
{
_id: 1,
parent_field: {
baby_field: value
}
}
/** Inside async function */
...await Model.findByIdAndUpdate(id, { "parent_field.baby_field": value })
Also, take a look at [`{overwrite: true}`](https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdate) option. It might be useful to you.
I faced the same issue, In my case, the defined mongoose schema for that model did not match the nested Object I was passing to the findByIdAndUpdate method. let me simplify it, here is the model
import { model, Schema } from 'mongooose';
const UserModel = model('user', new Schema({
profile: {
avatar: String,
bio: String,
}
}));
And here is the update query:
async function getDefaultProfile() {
const user = await UserModel.findById(process.env.DEFAULT_USER);
return user.profile;
}
const profile = await getDefaultProfile();
UserModel.findByIdAndUpdate('user id', {
$set: {
profile: profile
}
});
The important note was that my getDefaultProfile function returns a mongoose nested object, not a pure object. So in the returned object, I had $set, $get, etc function. So as you know this object is not what we define in the mongoose model, therefore the mongoose ignores it.
So I guess you have the same problem, or something close to my issue.
What should I do?
Run your project in debugging mode.
then check req.body or whatever that gives you the nested object (in my case getDefaultProfile).
Check it with your model, Are they equal?
And if that solution does not work for you, please try this solution, write a utility function:
export async function flatObjectAndSeparateThemByDot(
object: any,
): Promise<any> {
const res: any = {};
(function recurse(obj: any, current?: string) {
for (const key in obj) {
const value = obj[key];
// joined keys with dot
const newKey = current ? current + '.' + key : key;
if (value && typeof value === 'object') {
// it's a nested object, so do it again
recurse(value, newKey);
} else {
// it's not an object, so set the property
res[newKey] = value;
}
}
})(object);
return res;
}
then you can pass your nested object to this function and you will get something like this: { "profile.avatar": "lorem ipsum", "profile.bio": "bio temp" }. So to show you how this function works I will write a sample code:
const sampleProfile = {
profile: {
avatar: "asd",
bio: "sample"
}
}
const profile = await flatObjectAndSeparateThemByDot(sampleProfile);
await UserModel.findByIdAndUpdate('user id', {
$set: {
// other fields,
...profile
}
});
I'm trying to save some objects into an array by looping through a list of songs in an album, looking for relevant songs and trying to save into array for later use. is there any way to achieve this?
I need some explanation using mongoose.
exports.playlistPlayer = function (req, res, next) {
Playlist.findById({
_id: req.body.playlist._id
}, (err, playlist) => {
var customAlbum = []; //This variable it's inside the same block i believe
playlist.songs.forEach(function (song) {
Song.findById({
_id: song.song_id
}, (err, songs) => {
var customSong = {
title: songs.title,
time: songs.time,
source: songs.source,
song_id: songs._id
}
customAlbum.push(customSong)
console.log(customAlbum) //it works here
});
});
console.log(customAlbum) //it returns an empty array here where i need the data
});
};
The problem is that the findById method is also asynchronous. I recommend you to learn about promises in javascript. One possible solution would be using the async/await feature from ES7:
// asynchronous function
exports.playlistPlayer = async (req, res, next) => {
// wait for the findById method promise to resolve
const playlist = await Playlist.findById({
_id: req.body.playlist._id
})
// wait for finding all songs in db whose id's are in
// the playlist.songs array
const songs = await Song.find({
_id: { $in: playlist.songs }
})
// create the customAlbum by using the map method to
// tramsform the song objects to the required form
const customAlbum = songs.map(song => ({
title: song.title,
time: song.time,
source: song.source,
song_id: song._id
}))
// and there you should now have your customAlbum array
console.log(customAlbum)
// now you can use it for example
// to return a response to the client:
// res.json(customAlbum)
}
Hope for your help.
I have collection tasks with document like this schema.
Task = {
title:'taskName',
performers:[ {userId:1,price:230}, {userId:2,price:260} ]
}
Profiles = { id:1, name: 'Alex', surname: 'Robinson', etc.. }
Finally, I shoul collect all data, and in response return Array of profiles objects. Problem is that for-loop end before finished all .findOne() for every elements, and it return empty Array.
This is code form get.
CODE HERE:
apiRoutes.get('/performers/:id', function(req,res,next){
var profArr = [];
Task.findOne({'_id':req.params.id},function(err, doc){
for(var i = 0; i<doc.performers.length; i++){
var profile = {
price: 0,
name: '',
surname: ''
};
profile.price = doc.performers[i].price;
Profile.findOne({'_id':doc.performers[i].userId},function(err,doc){
if (err) throw err;
profile.name = doc.name;
profile.surname = doc.surname;
profArr.push(profile);
});
}
return res.json({success:true,
message:'Performers data collected',
data:profArr});
});
The problem is you need to return response inside mongoose query. You can't use any values assigned inside the query outside. For example :
var sampleArr = [];
Users.find({}, function(err, users) {
users.forEach(function(user) {
Students.find({'userid' : user.id}, function(err, student) {
sampleArr.push({
'student' : student
});
})
console.log(sampleArr);
// It will only return empty array[];
})
})
So, your task should be like this:
apiRoutes.get('/performers/:id', function(req,res,next){
var profArr = [];
// Get a task by ID
Task.findById(req.params.id, function (err, task) {
// Get all profiles
Profile.find({}, function (err, profiles) {
task.performers.forEach(function(taskPerformer) {
profiles.forEach(function(profile) {
// Check the performer ID is the same with userID or not
if (profile._id == taskPerformer.userId) {
profArr.push({
price: taskPerformer.price,
name: profile.name,
surname: profile.surname
});
}
})
});
return res.json({
success:true,
message:'Performers data collected',
data:profArr
});
});
})
A simple idea would be to introduce a countdown-counter before you start your for-loop like this:
var countdown = doc.performers.length;
Decrement the countdown in the callback function of the findOne-Calls. Check if you have reached 0 and call a function outside to send the result.
But still your code doesn't look very efficient. There are a lot of calls to the db. Maybe you could rethink your datamodel in order to minimize the calls to the db.
Your "for" loop will be finished before findOne will be finished.