Sorting MapReduce results on MongoDB - node.js

I have my MapReduce working correctly to group my results by date. All works well, however I'd like to have the results to be returned from Most Recent to Oldest based on 'created' value.
findTimelineByQuery: function (query, fields, options, callback) {
var obj = {};
obj.map = function() {
emit(Date.UTC(this.created.getFullYear(), this.created.getMonth(), this.created.getDate()), {
created:this.created,
title:this.title,
type: this.type,
id: this._id,
owner: this.owner,
value: this.value
});
};
obj.reduce = function(previous, current) {
var array = [];
var res = {items:array};
current.forEach(function (v) {
res.items.push(v);
});
return res;
};
obj.verbose = true;
obj.query = query;
_Items.mapReduce(obj, function(error, model, stats) {
callback(model);
});
}

Related

Expressjs does not execute sequencially and function return does not work

I am new to node/express js, and trying to execute the following. The control executes the lines after function call "var nextVersion =getNextContractVersion(cid)", even before the function returns a response. As a result the value for newVersion is not updated to Contract object. Also, function getNextContractVersion(cid) returns undefined, unlike the updated nextVersion.
How do i fix this behavior, please suggest. Also, is the right way of invoking function?
// Package imports
const express = require('express');
var router = express.Router();
const mongoose = require('mongoose');
//Local imports
var { Customer } = require('../models/customer');
var { Contract } = require('../models/contract');
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
var nextVersion =getNextContractVersion(cid);
var contract;
if (validateCustomerId(cid)) {
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
});
contract.save((err, docs) => {
if (!err) {
Customer.findOneAndUpdate({ customerID: cid }, { $push: { contract: contract } },
{ safe: true, upsert: true, new: true }).populate({ path: 'contract' }).exec((err1, docs1) => {
if (!err1) {
res.send(docs1).status(200);
} else {
console.log('Error is adding a new contract:' + JSON.stringify(err1, undefined, 2));
}
});
} else {
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
}
});
} else {
res.status(400).send('Bad Request - Invalid input!')
}
});
function getNextContractVersion(cid) {
var nextVersion=1;
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
return nextVersion;
}
You are mixing synchronous and asynchronous code.
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
The above code effectively says "Go to the database, find one of these objects and whenever in the future that is done, run this code that's in the exec block."
One of the ways to reason about asynchronous code from a synchronous mindset is that of promises.
Here's a semi pseudo implementation:
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
return getTheMostRecentContract(cid)
.then(function(oldContract){
var nextVersion = oldContract.version +1;
if(!validateCustomerId(cid)){
return res.status(400).send('Bad Request - Invalid input!');
}
var contract;
var savePromises = [];
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
savePromises.push(contract.save());
});
return Promise.all(savePromises);
})
.then(function(resultOfAllSavePromises){
//rest of code here
}).catch(function(error){
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
return res.status(400);
})
});
function getTheMostRecentContract(cid) {
return Contract.findOne({ customerID: cid }).sort({version: 'descending'});
}
As a matter of practice though, have the database control your auto-increment values. This code won't work in a high traffic environment.

sorting not working in mongodb

my script :
function getAllExperiences(){
return new Promise(function(resolve,reject){
var perPage = 10
var page = req.params.page || 1
var type = req.body.type;
var sortAs = req.body.sortAs;
Experiences
.find({status:'live'})
.sort({type:sortAs})//,{duration:durationValue}
.skip((perPage * page) - perPage)
.limit(perPage)
.exec(function(err, results) {
Experiences.count().exec(function(err, count) {
if (err)
{
reject(err);
}else{
var obj =
{
experiences: results,
current: page,
pages: Math.ceil(count / perPage)
};
resolve(obj);
}
})
})
});
}
i am sorting according. price, duration, ratings
when i set
var type = 'price';
var sortAs = -1;
if i set
var type = 'price';
var sortAs = 1;
if i set
var type = 'duration';
var sortAs = -1;
or
var type = 'duration';
var sortAs = 1;
All condition gives me same result. i thing type is not accepting. what am i doing wrong.
function getAllExperiences(){
return new Promise(function(resolve,reject){
var perPage = 10
var page = req.params.page || 1
var type = req.body.type;
var sortAs = Number(req.body.sortAs);
var sort = {};
sort[type] = sortAs;
Experiences
.find({status:'live'})
.sort(sort)//,{duration:durationValue}
.skip((perPage * page) - perPage)
.limit(perPage)
.exec(function(err, results) {
Experiences.count().exec(function(err, count) {
if (err)
{
reject(err);
}else{
var obj =
{
experiences: results,
current: page,
pages: Math.ceil(count / perPage)
};
resolve(obj);
}
})
})
});
}
What you are trying to do is
{ type: sortAs }
where type is being used as object key, what you should do is
{ [type]: sortAs }
Here type is being used as variable to create dynamic key for the object. thus this will create.
{duration: "-1"}
Following code should work for you.
function getAllExperiences(){
return new Promise(function(resolve,reject){
var perPage = 10
var page = req.params.page || 1
var type = req.body.type;
var sortAs = req.body.sortAs;
Experiences
.find({status:'live'})
.sort({ [type] : sortAs })//, using es6 for generating dynamic property
.skip((perPage * page) - perPage)
.limit(perPage)
.exec(function(err, results) {
Experiences.count().exec(function(err, count) {
if (err)
{
reject(err);
}else{
var obj =
{
experiences: results,
current: page,
pages: Math.ceil(count / perPage)
};
resolve(obj);
}
})
})
});
}
You should change .sort({type:sortAs})//,{duration:durationValue} to
var sortDefinition = {};
sortDefinition[type] = sortAs;
//then in pipeline
.sort(sortDefinition)
Because in your example it will always try to sort by field with name typesince it's a key name in that code.

Confused with promises in a loop

I'm writing a dynamodb code that does the below.
Scan a particular column and get the values and add it to an array
convert the array to set and back to array to get the unique values
Loop through this set values as a parameter and get the actual value.
Basically trying to create a group by in DynamoDb.
Here the 1st and 2nd step I'm able to do it. but coming to step 3 I've a loop and inside the loop the code has to be executed and my code is as below.
var AWS = require("aws-sdk");
var creds = new AWS.Credentials('akid', 'secret', 'session');
AWS.config.update({
"accessKeyId": "myAccessId",
"secretAccessKey": "MySecretAccessKey",
"region": "us-east-1"
});
var dynamodb = new AWS.DynamoDB.DocumentClient();
var params = {
TableName: "MyTable",
FilterExpression: "#target_state = :target_state",
ExpressionAttributeNames: {
"#target_state": "target_state"
},
ExpressionAttributeValues: {
":target_state": "5"
}
};
var array = [];
dynamodb.scan(params).promise().then(function (data) {
data.Items.forEach(function (itemData) {
array.push(itemData.ruleNo)
});
console.log(array);
return array;
}).then(() => {
console.log("Entered 2nd block " + [...new Set(array)]);
var array2 = [...new Set(array)];
for (index = 0; index < array2.length; ++index) {
console.log(array2[index]);
var params1 = {
TableName: "ChemicalData",
FilterExpression: "#target_state = :target_state and #ruleNo=:ruleNo",
ExpressionAttributeNames: {
"#target_state": "target_state",
"#ruleNo": "ruleNo"
},
ExpressionAttributeValues: {
":target_state": "5",
":ruleNo": array2[index]
}
};
return dynamodb.scan(params1).promise().then(function (data) {
var uw = JSON.stringify((data.Items));
return uw;
});
}
}).then((data) => {
console.log(data);
}).catch(err => {
console.log(err)
})
when I run this program, the result that I get is only one value, and that is the first array value, I'm unable to know on how can I loop through all the array variables and then do a console.log(data). please let me know on where am I going wrong and how can I fix this.
Thanks
Using return inside for breaks the loop. You should gather promises from inner scan into array and use Promise.all to resolve then together
dynamodb.scan(params).promise().then(function (data) {
data.Items.forEach(function (itemData) {
array.push(itemData.ruleNo)
});
console.log(array);
return array;
}).then(() => {
console.log("Entered 2nd block " + [...new Set(array)]);
var array2 = [...new Set(array)];
var results = []; //results array
for (index = 0; index < array2.length; ++index) {
console.log(array2[index]);
var params1 = {
TableName: "ChemicalData",
FilterExpression: "#target_state = :target_state and #ruleNo=:ruleNo",
ExpressionAttributeNames: {
"#target_state": "target_state",
"#ruleNo": "ruleNo"
},
ExpressionAttributeValues: {
":target_state": "5",
":ruleNo": array2[index]
}
};
// push results to be resolved later
results.push(dynamodb.scan(params1).promise().then(function (data) {
var uw = JSON.stringify((data.Items));
return uw;
}));
}
// return promise that resolves when all results resolve
return Promise.all(results);
})

async each is running to fast?

I have the following code running through an object with async:
async.each(Object.keys(shopList), function(key, callback){
var shop = shopList[key];
saveOrder(payId, shopList[key], key, req.body, req.user, function(err, newOrder){
if (err) {
callback(err);
}else{
orderCount++;
console.log("succes!", orderCount, newOrder.number);
callback();
}
})
}, function(err){
if (err) {
console.log("ERROR!", err);
}else{
console.log("done!");
}
})
In this function a another function is called. This code looks like this:
saveOrder = function(payId, shop, nameSlug, body, user, callback){
console.log("saveOrder");
var orderNumber = 0;
Order.findOne().sort({_id:-1}).exec(function(err, latestOrder) {
if(latestOrder.number){
orderNumber = latestOrder.number.split("-")[1];
}
var order = new Order();
var date = new Date();
order.number = date.getFullYear().toString() + date.getMonth().toString() + "-" + (parseInt(orderNumber)+1);
order.date = date;
order.payId = payId;
order.status = {
status: "Created",
comment: "",
date: new Date()
};
order.comment = body.comment;
order.shop = {
name: shop.name,
nameSlug: nameSlug
}
order.billingDetails = {
//order details
}
order.sendDetails = {
//more order details
}
order.user = {
//yep, order details
}
var orderItems = [];
for(p = 0; p < shop.items.length; p++){
var product = shop.items[p];
var orderItem = {
_id: product._id,
name: product.name,
brand: product.brand[0].name,
price: product.price,
quantity: product.quantity
}
orderItems.push(orderItem);
}
order.items = orderItems;
order.save(function(err, result){
if (err){
console.log("err!", err);
return callback(err)
}else{
console.log("saved!");
return callback(null, result);
}
})
})
}
The problem is in the last function. There I try to create a ordernumber which must be unique. I get the last order, split the ordernumber and do a +1.
When I have more objects in my shopList, this function is triggered when he is not ready. With other words, the first order isn't saved then, and I will get the same ordernumber.
How can I fix this? I tried a setTimeout in the async.each but that isn't working.
You could use a mutex using locks.
The callbacks will wait that the mutex is unlocked to lock it making that you won't have simultaneous executions.
var locks = require('locks');
var mutex = locks.createMutex();
saveOrder = function(payId, shop, nameSlug, body, user, callback){
mutex.lock(function () {
console.log("saveOrder");
var orderNumber = 0;
Order.findOne().sort({_id:-1}).exec(function(err, latestOrder) {
if(latestOrder.number){
orderNumber = latestOrder.number.split("-")[1];
}
var order = new Order();
var date = new Date();
order.number = date.getFullYear().toString() + date.getMonth().toString() + "-" + (parseInt(orderNumber)+1);
order.date = date;
order.payId = payId;
order.status = {
status: "Created",
comment: "",
date: new Date()
};
order.comment = body.comment;
order.shop = {
name: shop.name,
nameSlug: nameSlug
}
order.billingDetails = {
//order details
}
order.sendDetails = {
//more order details
}
order.user = {
//yep, order details
}
var orderItems = [];
for(p = 0; p < shop.items.length; p++){
var product = shop.items[p];
var orderItem = {
_id: product._id,
name: product.name,
brand: product.brand[0].name,
price: product.price,
quantity: product.quantity
}
orderItems.push(orderItem);
}
order.items = orderItems;
order.save(function(err, result){
if (err){
console.log("err!", err);
return callback(err)
}else{
console.log("saved!");
return callback(null, result);
}
})
})
mutex.unlock(); //don't forget to unlock the mutex
});
}
You should use async.waterfall instead of async.each, because:
async.waterfall - runs the tasks array of functions in series, each passing their results to the next in the array. http://caolan.github.io/async/docs.html#waterfall
async.each - applies the function iteratee to each item in coll, in parallel.
Fixed this issue with using eachSeries() instead of each()
http://caolan.github.io/async/docs.html#eachSeries

ejs returning empty array but console.log returns an value

I have a variable that returns fine in console.log() but when rendering it returns an empty array.
function loadFestival(festSlug) {
return Q.Promise(function(resolve, reject, notify) {
Collection.findOne({
slug: festSlug
}).deepPopulate('events shows')
.exec(function(err, model) {
if (err) {
return reject(err);
}
if (!model) {
return reject(new Error('Festival not found for slug: ' + festSlug));
}
var show_performances = [];
model.shows.forEach(function(show) {
var showID = show._id.toString();
show_performances[showID] = []
show_performances[showID]['performances'] = [];
show_performances[showID]['ticketing'] = [];
model.events.forEach(function(event) {
if (show._id.toString() == event.show.toString()) {
show_performances[showID]['performances'].push(moment(event.dateRange.start).format('YYYY MMM D'));
show_performances[showID]['ticketing'][moment(event.dateRange.start).format('YYYY MMM D')] = event.ticketing_url;
}
});
});
resolve(show_performances);
});
});
}
From comments:
//Here is the code running
loadFestival('slug').then(function(model) {
console.log(model) //returns as should be
res.render('.//index.html', {
layout: layout,
model: JSON.stringify(model) // returns nothing
});
});
The above code returns as it should be in console.log(show_performances) but when rendering nothing returns. Thanks.

Resources