I am struggling with Parse Cloud code. I am trying to create a list of breweries based on a user reviews. I think I would need to calculate the average review for each brewery and then use this value to generate the list. I want to only include breweries with an average of 4 or higher.
Please forgive my ignorance on the matter, thanks in advance!
This is what I have and it doesn't work.
Parse.Cloud.afterSave("reviewAvergae", function(request){
var Review = Parse.Object.extend("Reviews");
var query = new Parse.Query("Reviews");
query.get(request.object.get("rating").id, {
success: function(reviews) {
var sum = 0;
for (var i = 0; i < reviews; i++) {
sum += reviews[i].get("rating");
}
var ave = sum / reviews.length;
reviews.set("average", ave);
reviews.save();
}
});
/*
query.equalTo("breweryId", request.params.brewId);
query.find({
success: function(result) {
var sum = 0;
for (var i = 0; i < result.length; i++) {
sum += result[i].get("rating");
}
Review.set("averageRating", sum / result.length);
Review.save();
console.log(sum / result.length);
}
});
*/
});
Parse.Cloud.define("featured", function(request, response) {
var query = new Parse.Query("Reviews");
// query.near("location", request.params.loc);
query.withinMiles("location", request.params.loc, 50);
query.limit(10);
query.find({
success: function(results) {
var sum = 0;
var rating;
var ratings = new Array();
var id;
var ids = new Array();
for (var i = 0; i < results.length; i++) {
id = results[i].get("breweryId");
ids.push(id);
}
response.success(results);
console.log(ratings.length);
},
error: function() {
response.error("No breweriers in your area have been reviewed.");
}
});
});
On parse my Review Object looks like this:
Note: My actual app is for iOS and written in swift.
Update: I have tried the following after adding a brewery object. I now get the following error : [Error]: function not found (Code: 141, Version: 1.7.5)
Parse.Cloud.afterSave("Reviews", function(request){
var Review = Parse.Object.extend("Reviews");
var breweryId = Review.get("breweryId");
var query = new Parse.Query("Reviews");
query.equalTo("breweryId", breweryId);
query.find({
success: function(results) {
var total = 0;
for (var i = 0; i < results.length; i++) {
var object = results[i];
total += object.get("rating");
}
var averageRating = total / results.length;
console.log(averageRating);
var query = new Parse.Query("Brewery");
query.equalTo("breweryId", breweryId);
}, error: function() {
}
});
});
here are some updated images of my my Objects in Parse
Understand from the comments that there's an external system with Brewery objects. In order to do parse.com computations about breweries, it will be necessary to represent them internal to parse, even if it is basically a pointer to an object in another system.
So, add a "Brewery" object. It will get an objectId from parse, but you should add a "breweryId" string attribute, as you have with your Review class. It should also have an "averageRating" number attribute.
Now we need logic to compute and save the average rating, given a breweryId (that's the external id, not the parse objectId)...
// a very helpful toolkit for dealing with js, especially arrays
var _ = require('underscore');
function updateAverageRatingForBreweryWithId(breweryId) {
var average;
// get all of the reviews for this breweryId
var query = new Parse.Query("Review");
query.equalTo("breweryId", breweryId);
return query.find().then(function(reviews) {
// pull out each review's rating attribute
var ratings = _.map(reviews, function(review) { return review.get("rating"); });
// this is just an average computation, summing the ratings and dividing by their count
average = _.reduce(ratings, function(memo, num) { return memo + num; }, 0) / (ratings.length === 0 ? 1 : ratings.length);
// go get the parse.com Brewery object for this breweryId
// we will save the average rating there
var query = new Parse.Query("Brewery");
query.equalTo("breweryId", breweryId);
return query.first();
}).then(function(brewery) {
brewery.set("averageRating", average);
return brewery.save();
});
}
This function takes an external breweryId as input, gets the reviews for that brewery, computes the average, then finds (your new) Brewery object with that breweryId sets its averageRating and saves it.
A good place to call this is after saving a Review, which is what it looked like you were up to in the original post. That afterSave must be named (first parameter) "Review" if you want it to work on the Review object (not the name you gave it or "Comment", which #RyanKreager mentions for some reason)...
Parse.Cloud.afterSave("Review", function(request) {
var review = request.object;
var breweryId = review.get("breweryId");
updateAverageRatingForBreweryWithId(breweryId);
});
Now with a Brewery object we're in a position to query it with the real goal: what are the highly rated breweries near me? I can add some additional advice for that, but it only makes sense if you create a Brewery object.
I see a couple of errors:
You should be storing the average field on the Brewery object, not the review. That way you just update one object and you can do a nice, tidy query for all the Brewery objects where average > x etc.
breweryId is a string. This should be a pointer to the Brewery object. Also change the name to just brewery
Once you have those two things cleared up, something like this makes more sense for the afterSave on a Review object:
Your afterSave needed some adjustments. Try this:
Parse.Cloud.afterSave("Comment", function(request) {
var Review = Parse.Object.extend("Review");
var query = new Parse.Query(Review);
query.equalTo("brewery", request.object.get("brewery"));
query.find({
success: function(results) {
// Find the average from all the brewery reviews
var total = 0;
for (var i = 0; i < results.length; i++) {
var object = results[i];
total += object.get("rating");
}
var averageRating = total / results.length;
var updatedObjects = [];
for (var i = 0; i < results.length; i++) {
var object = results[i];
object.set("average", averageRating);
updatedObjects.push(object);
}
Parse.Object.saveAll(updatedObjects);
}, error: function(error) {
alert("Error: " + error.code + " " + error.message);
});
});
Related
I am just starting with NetSuite and trying to pull all items with details using Restlet. With some research, I am able to pull all the items but the way I am doing now is not straightforward. I first pull the all ids of item using nlapiSearchRecord and loop through each id to get details of each item using nlapiLoadRecord and added to array. This way, it is taking to much time. Is there other way to pull all items with their details? Below is my code.
function getAllIDs() {
return nlapiSearchRecord('item', null, null, null);
}
function getRecord() {
var all_IDs = getAllIDs();
var len=all_IDs.length;
var result =new Array();
for(var i=0;i<all_IDs.length;i++) {
if(all_IDs[i].getRecordType()==="inventoryitem")
result[i]=nlapiLoadRecord(all_IDs[i].getRecordType(),all_IDs[i].id)
}
return result;
}
You can use what #Krypton suggested but you will always get 1000 results at max.
Try following if you have requirement to get more than 1000 (using Suitescript 2.0):
var columns = [];
var filters = [['isinactive', 'is', 'F']];
columns.push(search.createColumn({ name: "itemid"}));
columns.push(search.createColumn({ name: "displayname"}));
columns.push(search.createColumn({ name: "salesdescription"}));
columns.push(search.createColumn({ name: "baseprice"}));
var inventoryitemSearch = search.create({
type: search.Type.INVENTORY_ITEM, //Change the type as per your requirement
filters: filters,
columns: columns
});
var arrResults = [];
var count = 1000;
var startIndex = 0;
var endIndex = 1000;
var resultSet= inventoryitemSearch.run();
while (count == 1000) {
var results = resultSet.getRange(startIndex, endIndex);
arrResults = arrResults.concat(results);
startIndex = endIndex;
endIndex += 1000;
count = results.length;
}
log.debug({title: 'arrResults ', details: arrResults });
You can include the details you want in the search. So, for example, you can include an nlobjSearchFilter so that the search only returns inventory items, and add an nlobjSearchColumn for each field you want to see in the details. This way all the details you want to see are returned with the search and you can loop through the results to do what you want with them without loading every record individually - which will be where most of the performance hit is happening.
An example:
var inventoryitemSearch = nlapiSearchRecord("inventoryitem",null,
[
["type","anyof","InvtPart"]
],
[
new nlobjSearchColumn("itemid",null,null).setSort(false),
new nlobjSearchColumn("displayname",null,null),
new nlobjSearchColumn("salesdescription",null,null),
new nlobjSearchColumn("baseprice",null,null)
]
);
Then you can loop through the results to get details:
var name, displayName, description, price;
for ( var i = 0; inventoryitemSearch != null && i < searchresults.length; i++ ) {
var searchresult = inventoryitemSearch[ i ];
name = searchresult.getValue( 'itemid' );
displayName = searchresult.getValue( 'displayname' );
description = searchresult.getValue( 'salesdescription' );
price = searchresult.getValue( 'baseprice' );
}
There is a lot to learn about scripted searches in NetSuite, so I'd recommend starting here (NetSuite login required) and follow the links and keep reading / experimenting until your eyes glaze over.
I just like to use a generic function that accepts a search object...
const getAllResults = searchObj => {
try {
const Resultset = searchObj.run()
const maxResults = searchObj.runPaged().count
let ResultSubSet = null
let index = 0
const maxSearchReturn = 1000
let AllSearchResults = []
do {
let start = index
let end = index + maxSearchReturn
if (maxResults && maxResults <= end) {
end = maxResults
}
ResultSubSet = Resultset.getRange(start, end)
if (ResultSubSet.length === 0) {
break
}
// we could intriduce a record processor to lighetn up the load
AllSearchResults = AllSearchResults.concat(ResultSubSet)
index = index + ResultSubSet.length
if (maxResults && maxResults == index) {
break
}
} while (ResultSubSet.length >= maxSearchReturn)
return AllSearchResults
} catch (e) {
log.error(`getAllResults()`, `error : ${e}`)
}
}
Before there are any questions, I have a function that creates a custom UID because I don't "need" the long Firebase UID and wanted something shorter which is more easy to remember.
Anyway, in my create-function I'm not sure I'm adding it to the db correctly.
Here's a snippet and below that is how it looks in my database.
$scope.create = function(){
// Generate a shorter random uid (6 chars) that replaces the long regular Firebase uid
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
uid_length = 6;
generator = function(){
random = '';
for(var i = 0; i < uid_length; i++){
random += letters.charAt(Math.floor(Math.random() * letters.length));
}
return random;
}
generator();
var lists = new Firebase('https://url.firebaseio.com/lists/' + random);
firebaseLists = $firebaseArray(lists);
//lists.child(random).set(random);
firebaseLists.$add(random).then(function(lists){
})
This gives me, for example:
lists
0Knn8M <- custom UID
-KH6kSxPAaerjU: '0Knn8M'
As I want the things that are added to 0Knn8M displayed on my page, it also displays the FB UID. Of course I could do a CSS 'display:none;' on that child but shouldn't it be another way around it?
I think, that here is an error:
generator = function(){
random = '';
for(var i = 0; i < uid_length; i++){
random += letters.charAt(Math.floor(Math.random() * letters.length));
}
return random;
}
generator();
var lists = new Firebase('https://url.firebaseio.com/lists/' + random);
You are calling generator() but not assigning it result to any variable. In var lists = new Firebase('https://url.firebaseio.com/lists/' + random); the variable random is now undefined. So firebase generates it's own id I guess.
So you need to change just one thing var random = generator().
I have this script from Google Developer site that fetches all the domain users:
function listAllUsers() {
var pageToken, page;
do {
page = AdminDirectory.Users.list({
domain: 'wter.se',
orderBy: 'givenName',
maxResults: 100,
pageToken: pageToken
});
var users = page.users;
if (users) {
for (var i = 0; i < users.length; i++) {
var user = users[i];
}
}
pageToken = page.nextPageToken;
} while (pageToken);
}
I would like to get each users parameters and add them to a spreadsheet.
The variable users have all these parameters (users.EmailAdress etc), but how do i put them in a spreadsheet?
You have to build an array of arrays (a 2D array) with one array for each row in the sheet, here is an example with 3 columns that list all the users of my domain using the simple UserManager class.
The code structure is pretty much the same, a for loop and users[i] properties...
The output array here is called 'r' and is written in the sheet using the final setValues(r); - the code has also a sorting function that you don't need but I left it here for info if ever someone needs it.
function listUsers(s) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sheet.getDataRange().clear()
var users = UserManager.getAllUsers();
var r = new Array();
var f = new Array();
for( var i = 0 ; i < users.length ; i++ ){
var Umail = users[i].getEmail();
var UnomF = users[i].getFamilyName()
var UnomP = users[i].getGivenName()
r.push([UnomF,UnomP,Umail]);
}
r.sort(function(x,y){
var xp = x[0].toLowerCase();
var yp = y[0].toLowerCase();
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on name ascending
}
)
var header = ['Nom de famille','Prénom','Email']
sheet.getRange(1,1,1,r[0].length).setValues([header]).setFontWeight('bold')
.setBackground('silver').setBorder(true,true,true,true,true,true);
sheet.getRange(2,1,r.length,r[0].length).setValues(r);
}
I have this code. I want to loop through all Users in a database and for each user get a list of their Portfolios (which in turn is a collection of stocks):
calculatePortfolios: function(callback) {
var thisuser;
module.exports.getAllUsers(function(err, users) {
/* Loop through all users with i */
for(var i = 0; i < users.length; i++) {
console.log('i = ' + i);
console.log(users.length);
thisuser = users[i]; // Store current user.
/* Loop through all held stocks in current user's portfolio. */
module.exports.getPortfolio(thisuser.username, function(err, portfolio) {
for(var x = 0; x < portfolio.length; x++) {
console.log('x = ' + x);
var total = parseFloat((portfolio[x].held * portfolio[x].unitprice) + thisuser.cash);
console.log(total);
console.log(thisuser.username);
module.exports.updateInvestor(thisuser.username, total, function(err, result) {
console.log(thisuser.username + ' ' + total);
});
}
});
}
callback(err, 'OK');
});
},
The result I get is that all i indices (users) are looped through before all x indices (portfolios). Shouldn't x be an inner loop of i?
Is this something related to how Node.JS works?
Any help is much appreciated. Thank you.
the getPortfolio() is most likely an asynchronous operation, and depending on the internal implementation it may either queue the calls or translate them to http requests for example that may take time, when these requests are completed only then your callback function that operates with x will be called.
But meanwhile getPortfoli() call will return. That is exactly why you see it iterating fast over all your i's, and only then you callback starts getting called.
Lack of JOIN's is really confusing to users who come with SQL background to node.js (event-driven) and NoSQL databases.
Consider this flow for your task:
Load all users.
Iterate through each user and record field which is used for collection relationship (username in your case), usually it is _id.
2b. (Optional) make Object, where key - will be field of relationship, in your case username, and value - will be object it self.
Make another query to related collection with similar query: { _id: { $in: ids } } where ids will be array of user _id's. In your case you need list of usernames.
Iterate through each portfolio item and add it to user object. If 2b did - then no need of two iterations.
Users are done, and have portfolio data and ready to be used.
#PSL: Got it working with async.waterfall. Probably not the most elegant solution, but it works for now.
/**
* #description Calculate the value of an investor's portfolio.
* #function
*/
calculatePortfolios: function(callback) {
async.waterfall([
function(callback) {
var allUsers = [];
module.exports.getAllUsers(function(err, users) {
for(var i = 0; i < users.length; i++) {
allUsers.push(users[i]);
}
});
callback(null, allUsers);
},
function(allUsers, callback) {
var totals = [];
for(var i = 0; i < allUsers.length; i++) {
module.exports.getPortfolio(allUsers[i].username, function(err, portfolio) {
for(var x = 0; x < portfolio.length; x++) {
totals.push(parseFloat(portfolio[x].held * portfolio[x].unitprice));
}
});
}
callback(null, allUsers, totals);
},
function(allUsers, totals, callback) {
for(var i = 0; i < allUsers.length; i++) {
module.exports.updateInvestor
(allUsers[i].username, (totals[i] + allUsers[i].cash), function(err, result) {
console.log('Updated an investor!');
});
}
callback(null, 'OK');
}
]);
callback(null, 'OK');
},
I need to sort through a data set which as you can see I've assigned to the records variable. From that data I need to see if the zip code exists. If the zip code does not exist then I need to move it into the array (There of course will be duplicates) and continue checking the rest of the records, if it does exist I need to do nothing.
// Declare Array
var numbersArray = [];
// Variables
var records;
var zipCode;
var numbers;
var index;
var output;
var outputMessageOne;
var outputMessageTwo;
var count = 0;
output = document.getElementById('outputDiv');
records = openZipCodeStudyRecordSet();
output.innerHTML = "The unique zip codes are: ";
while (records.readNextRecord()) {
zipCode = records.getSampleZipCode();
for (index = 0; index < numbersArray.length; index++) {
if (zipCode === numbersArray[index]) {
var uniqueZip = false;
break;
records++;
}
if (zipCode !== numbersArray[index]) {
numbersArray.push(zipCode);
}
}
output.innerHTML += numbersArray;
}
}
You can simplify your for loop like so:
matchedZip = false;
for(i in numbersArray) {
if (numbersArray[i] === zipCode) {
matchedZip = true;
}
}
if ( ! matchedZip) {
numbersArray.push(zipCode);
}
Try plugging that into your while loop. If you have the array push inside of the for loop you're going to end up pushing each zip code in every time there is not a match.
Well, you didn't exactly ask a question, but I'll answer anyway :) The answer is that you should not use a normal array for this, but rather a map or associative array. Fortunately a plain Javascript object can be used for this:
var numbers = {};
// Variables
var records;
var numbers;
var index;
var output;
var outputMessageOne;
var outputMessageTwo;
var count = 0;
output = document.getElementById('outputDiv');
records = openZipCodeStudyRecordSet();
output.innerHTML = "The unique zip codes are: ";
while (records.readNextRecord()) {
var zipCode = records.getSampleZipCode();
numbers[zipCode] = 1; // just picking an arbitrary value
}
for (var zipCode: numbers) {
output.innerHTML += zip + " ";
}
The reason is that this way you don't need to loop through the existing data for each new input.