Failing Parse background job when using beforesave with thousands of objects - ios

I am using a background job to query a json with thousands of objects to initially populate my database. I have also implemented the beforesave function to prevent any duplicate entries. However, once I implemented this, it seems my background job called response.error and does not save all objects. It looks like I might be exceeding the requests/sec? I would really appreciate if someone could take a look at my code and tell me why it is not saving all entries successfully.
Here is my background job:
Parse.Cloud.job("testing", function(request, response) {
var json;
Parse.Cloud.httpRequest({
url: stringURL + pageNumber.toString(),
success: function(httpResponse) {
json = httpResponse.data;
console.log("total is: " + json["meta"].total);
console.log("object 1 is: " + json["events"][1].title);
return json;
}
//after getting the json, save all 1000
}).then(function() {
//helper function called
saveObjects(json).then(function() {
response.success("success");
},
function(error) {
response.error("nooooo");
});
});
});
function saveObjects(json) {
var promises = [];
for(var i = 0; i < 1000; i++) {
var newEvent = new Event();
promises.push(newEvent.save(new Event(json["events"][i])));
}
return Parse.Promise.when(promises);
}
Here is my beforesave code:
Parse.Cloud.beforeSave("Event", function(request, response) {
var newEvent = request.object;
var Event = Parse.Object.extend("Event");
var query = new Parse.Query("Event");
query.equalTo("title", newEvent.get("title"));
query.equalTo("datetime_utc", newEvent.get("datetime_utc"));
query.equalTo("url", newEvent.get("url"));
query.first({
success: function(temp) {
response.error({errorCode:123,errorMsg:"Event already exist!"});
},
error: function(error) {
response.success();
}
});
});
Thanks I really appreciate any help... I've been stuck for a while.

If it's a request rate issue, then you could probably use something like node-function-rate-limit but it's fairly simple to write your own rate limiting batcher. See doInBatches() below.
Also, when using promise-returning methods that also offer a "success:..." callback, it's better not to mix the two styles. It may behave as expected but you are denied the opportunity to pass results from the "success:..." callback to the rest of the promise chain. As you can see below, the "success:..." code has simply been shuffled into the .then() callback.
Parse.Cloud.job("testing", function(request, response) {
Parse.Cloud.httpRequest({
url: stringURL + pageNumber.toString()
}).then(function(httpResponse) {
var json = httpResponse.data;
// console.log("total is: " + json.meta.total);
// console.log("object 1 is: " + json.events[1].title);
/* helper function called */
doInBatches(json.events, 30, 1000, function(evt, i) {
var newEvent = new Event();
return newEvent.save(new Event(evt));
}).then(function() {
response.success('success');
}, function(error) {
response.error('nooooo');
});
});
});
// Async batcher.
function doInBatches(arr, batchSize, delay, fn) {
function delayAsync() {
var p = new Parse.Promise();
setTimeout(p.resolve, delay);
return p;
}
function saveBatch(start) {
if(start < arr.length) {
return Parse.Promise.when(arr.slice(start, start+batchSize).map(fn))
.then(delayAsync) // delay between batches
.then(function() {
return saveBatch(start + batchSize);
});
} else {
return Parse.Promise.as();
}
}
return saveBatch(0);
}
I can't see how or why the beforesave code might affect things.

Related

SAPUI5 oData.V2 How to invoke a function after everything in a batch request is done?

I have an issue while making an SAPUI5 odata V2 batch request :
var that = this;
var oServiceModel = that.getModel("oServiceModel");
odataMod = this.getModel("Service");
odataMod.setUseBatch(true);
var aData = oServiceModel.getData();
var stupidService = _.filter(aData, function (ae) {
return ae.Info === "-N/A";
});
var i = 0 ;
_.forEach(stupidService, function (sap) {
oGlobalBusyDialog.setText("Deleting service :" + sap.ObjectID);
oGlobalBusyDialog.setTitle("Deleting Service");
oGlobalBusyDialog.open();
that.removeService(sap).then(function () {
if (i === 615) {
oGlobalBusyDialog.close();
}
}).catch(function () {});
});
my Delete function is like this:
removeService: function (service) {
var that = this;
return new Promise(
function (resolve, reject) {
odataMod.remove('/ProjectTaskServiceCollection(\'' + service.ObjectID + '\')/', {
success: function (oData) {
resolve(oData);
},
error: function (oResult) {
that.handleError(oResult);
oGlobalBusyDialog.close();
reject(oResult);
}
});
});
What's happening ,is that if I'm trying to delete 500 entry, and if 200 entry cannot be deleted, the error message gets displayed 200 times
How to make it in a way to only display the error message once ?
Also, I want to turn off the batch request once everything is done odataMod.setUseBatch(false); how to do it ?
*EDIT: *
I've manage to do :
var aDeffGroup = odataMod.getDeferredGroups();
//add your deffered group
aDeffGroup.push("deletionGroup");
for (var s = 0; s < 5; s++) {
odataMod.remove('/ProjectTaskServiceCollection(\'' + stupidService[s].ObjectID + '\')/', {
//pass groupid to remove method.
groupId: "deletionGroup"
});
}
odataMod.submitChanges({
// your deffered group id
groupId: "deletionGroup",
success: function() {
//Get message model data from Core and it contains all errors
// Use this data to show in dialog or in a popover or set this to your local model see below code
var aErrorData = sap.ui.getCore().getMessageManager().getMessageModel();
console.log(aErrorData);
}
});
yet stills my console.log(aErrorData); still prints multiple error message
Instead of doing individual deletion odata calls. Add these all remove methods in a single group, then call odatamod.submitChanges() method.
Example:
//get all deffered groups
var aDeffGroup = odataMod.getDeferredGroups();
//add your deffered group
aDeffGroup.push("deletionGroup");
//set it back again to odatamodel
odataMod.setDeferredGroups(aDeffGroup);
odataMod.remove('/ProjectTaskServiceCollection(\'' + service.ObjectID + '\')/', {
//pass groupid to remove method.
groupId: "deletionGroup"});
odataMod.submitChanges({
// your deffered group id
groupId:"deletionGroup",
success: function() {
//Get message model data from Core and it contains all errors
// Use this data to show in dialog or in a popover or set this to your local model see below code
var aErrorData = sap.ui.getCore().getMessageManager().getMessageModel();
});

SAPUI5 processing a loop with promises

oModel.create("/SurveySet", oEntry, {
success: function(oData) {
for (var i = 0; i < questionData.questions.length; i++) {
var oEntry = {};
oEntry.SurveyId = oData.SurveyId;
oModel.create("/QuestionSet", oEntry, {
changeSetId: i.toString(),
success: function(oData) {
//Additional Processing
}
}
}
}
}
I'm Creating a SurveySet which returns a SurveyId.
For each Survey I need to create a QuestionSet in a FOR loop, returning the Data each time for additional processing.
The issue is in the order of execution. The for loop is incremented BEFORE the oData is retrieved from the 1st iteration.
E.g. if the for loop has 0 and 1, only the last element 1 is executed.
How do I delay the incrementation of the for loop until AFTER the oData is returned for the first iteratiion of the loop?
I guess you access oEntry or i in the "Additional Processing".
The problem is that the inner success function with the additional processing is capturing the local variables defined outside like i and oEntry in a closure. The variables values are not copied.
The for loop increments i, changes oEntry and executes the oModel.create() method.
Next loop: The for loop increments i again, changes oEntry and executes oModel.create() again.
When the loop is done or any time later after the request to the backend has completed, your inner success handlers will be called. They access the outer variables which have only survived that long because they were captured in the closure. And they will be in the state they were when the for loop has finished.
So if you don't mind that your additional processing might happen out of order you can move the code inside the for loop into a separate function. When you call that function from the for loop the variable values will be copied so that each before mentioned closure will capture the copies which will not be changed by the for loop:
createSurvey: function(oEntry){
var that = this;
oModel.create("/SurveySet", oEntry, {
success: function(oData) {
for (var i = 0; i < questionData.questions.length; i++) {
that.createQuestion(oModel, questionData.questions, i, oData);
}
}
}
}
createQuestion(oModel, questions, i, survey){
var oEntry = {};
oEntry.SurveyId = survey.SurveyId;
oModel.create("/QuestionSet", oEntry, {
changeSetId: i.toString(),
success: function(oData) {
//Additional Processing
}
}
}
PS: You can also use questionData.questions.forEach(function(question, i){ ... }); to get the same effect. This time the anonymous function copies the values.
If you need to maintain strict order for your additional processing and send the requests sequencially to the backend, i would indeed recommend the use of promises:
createSurvey: function(oEntry){
var that = this;
oModel.create("/SurveySet", oEntry, {
success: function(oData) {
var promise = Promise.resolve();
questionData.questions.forEach(function(question, i) { //copy local variables
//Chain the promises
promise = promise.then(function(){ return that.createQuestion(oModel, question, i, oData)});
});
promise.then(function(){
//All done
})
.catch(function(){
//Error somewhere. remaining not executed
})
}
}
}
createQuestion(oModel, question, i, survey){ //returns a Promise now
var oEntry = {};
oEntry.SurveyId = survey.SurveyId;
return new Promise(function(resolve,reject){ //Wrap the UI5 stuff into a Promise
oModel.create("/QuestionSet", oEntry, {
changeSetId: i.toString(),
success: function(oData) {
//Additional Processing
resolve(oData);
},
error: reject
}
});
}
You can push your Survey data to an array,then do the FOR loop
var data = [];
oModel.create("/SurveySet", oEntry, {
success: function(oData) {
data.push(oData.SurveyId);
}
}
$.each(data, function(index, value) {
for (var i = 0; i < questionData.questions.length; i++) {
var oEntry = {};
oEntry.SurveyId = value.SurveyId;
oModel.create("/QuestionSet", oEntry, {
changeSetId: i.toString(),
success: function(oData) {
//Additional Processing
}
}
}
)};

Why my md-autoComplete is not displaying return values

I am using Angular Material for the first time. I am stuck with an issue with autocomplete. Below is my template:
<md-autocomplete class="flex"
md-no-cache="true"
md-selected-item="c.receipt"
md-item-text="item.name"
md-search-text="SearchText"
md-items="item in querySearch(SearchText)"
md-floating-label="search">
<md-item-template>
<span><span class="search-result-type">{{item.GEOType}}</span><span md-highlight-text="SearchText">{{item.GEOName+(item.country?' / '+item.country:'')}}</span></span>
</md-item-template>
<md-not-found>No matches found.</md-not-found>
</md-autocomplete>
And in ctrl I have:
$scope.querySearch = function (query) {
var GeoDataAPIUrl = '/api/TargetSettings/RetrieveCorridorLeverValues';
if (query.length < 5)
return;
else {
var GeoDataSearchUrl = GeoDataAPIUrl + '?' + 'strGeoName=' + query;
$http
.get(GeoDataSearchUrl)
.then(function (geoAPIResponse) {
console.log("GeoAPIResponse was ", geoAPIResponse);
return geoAPIResponse.data;
},
function (geoAPIError) {
console.log("GeoAPI call failed ", geoAPIError);
});
}
};
With above code, I am getting nothing as suggestions, only my not-found text is displayed, while my http call return an array which is printed in console too. Am I missing something??
I saw at many places, people have used some filters with autocomplete, I dont think that is something essential.
Pls advice how to make above work.
$http returns promise and md-autocomplete uses same promise to display the result. In your case you are returning result but not promise. Your code should be
$scope.querySearch = function (query) {
var GeoDataAPIUrl = '/api/TargetSettings/RetrieveCorridorLeverValues';
if (query.length < 5)
return;
else {
var GeoDataSearchUrl = GeoDataAPIUrl + '?' + 'strGeoName=' + query;
var promise = $http.get(GeoDataSearchUrl).then(function (geoAPIResponse) {
console.log("GeoAPIResponse was ", geoAPIResponse);
return geoAPIResponse.data;
},
function (geoAPIError) {
console.log("GeoAPI call failed ", geoAPIError);
});
return promise;
}
};
It will work now.

breeze observableArray binding - are properties observable?

I have a viewmodel which consists of a list(foreach loop) of DoctorPrices and when clicking on an item in the list it open up a CRUD form on the side. However when i update the values on the CRUD the observableArray that is bound to the foreach is not refreshing? (although the values are updates in the DB correctly)
From my data access module i call the following query.
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
return manager.executeQueryLocally(query);
}
In my viewmodel i have the following code:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
services is bound using a foreach loop (not posting here as the code is simple and works)
When i click on a one of the DoctorPrices it gets the data as follows and places it in an observable:
this.selectedPrice = function (data, event) {
self.currentService(data);
self.showEdit(true);
};
I then bind selectPrice to a simple form that has the properties on it to be modified by the user. I then call manager.SaveChanges().
This results in the following problem: the value is being updated correctly but the GUI / Original List that is bound in the foreach is not being updated? Are the properties in breeze not observables? What is the best way to work with something like this.
I thought of a workaround and changing the code with something like this:
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
});
But i feel that clearing the array in that way is sloppy and not the right way of doing things specially with long lists.
Can someone please point me in the right direction on how to bind observableArray properties properly so they are updated?
Additional code my VM Component:
function services() {
var self = this;
this.showForm = ko.observable(false);
this.currentService = ko.observable();
this.services = ko.observableArray(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
this.title = ko.observable();
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
self.showDetails(false);
});
this.show = function (value) {
self.showForm(value);
};
this.showDetails = ko.observable(false);
this.addNewService = function() {
self.currentService(doctorServices.createService(doctorList.viewModel.instance.currentDoctorID()));
console.log(self.currentService().entityAspect.entityState);
self.showDetails(true);
};
this.showDelete = ko.computed(function() {
if (self.currentService() == null)
return false;
else if (self.currentService().entityAspect.entityState.isDetached()) {
self.title('Add new service');
return false;
} else {
self.title('Edit service');
return true;
}
});
this.deleteService = function() {
self.currentService().entityAspect.setDeleted();
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
};
this.closeDetails = function () {
doctorServices.manager.rejectChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.showDetails(false);
};
this.selectService = function (data, event) {
self.currentService(data);
self.showDetails(true);
};
this.saveChanges = function () {
console.log(self.currentService().entityAspect.entityState);
if (self.currentService().entityAspect.entityState.isDetached()) {
doctorServices.attachEntity(self.currentService());
}
console.log(self.currentService().entityAspect.entityState);
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.currentService.notifySubscribers();
self.showDetails(true);
};
}
return {
viewModel: {
instance: new services()
},
template: servicesTemplate,
};
Below is my Breeze Data Class:
define('data/doctorServices', ['jquery', 'data/dataManager', 'knockout','mod/medappBase', 'breeze', 'breeze.savequeuing'], function ($, manager, ko,base, breeze, savequeuing) {
var services = ko.observableArray([]);
return {
attachEntity:attachEntity,
getServices: getServices,
services: services,
manager:manager,
getDoctorServices: getDoctorServices,
getServiceById: getServiceById,
createService:createService,
hasChanges: hasChanges,
saveChanges: saveChanges
};
function getServices() {
var query = breeze.EntityQuery.from("DoctorPrices");
return manager.executeQuery(query).then(function (data) {
services(data.results);
}).fail(function (data) {
console.log('fetch failed...');
console.log(data);
});;
}
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
var set = manager.executeQueryLocally(query);
return set;
}
function getServiceById(serviceId) {
return manager.createEntity('DoctorPrice', serviceId);
//return manager.getEntityByKey('DoctorPrice', serviceId);
}
function handleSaveValidationError(error) {
var message = "Not saved due to validation error";
try { // fish out the first error
var firstErr = error.innerError.entityErrors[0];
message += ": " + firstErr.errorMessage;
base.addNotify('error', 'Could not save.', message);
} catch (e) { /* eat it for now */ }
return message;
}
function hasChanges() {
return manager.hasChanges();
}
function attachEntity(entity) {
manager.addEntity(entity);
}
function createService(doctorId) {
return manager.createEntity('DoctorPrice', { DoctorPricingID: breeze.core.getUuid(), DoctorID:doctorId }, breeze.EntityState.Detached);
};
function saveChanges() {
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(saveResult) {
base.addNotify('success', 'Saved.', 'Your updates have been saved.');
}
function saveFailed(error) {
var reason = error.message;
var detail = error.detail;
if (error.innerError.entityErrors) {
reason = handleSaveValidationError(error);
} else if (detail && detail.ExceptionType &&
detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
// Concurrency error
reason =
"Another user, perhaps the server, " +
"may have deleted one or all of the settings." +
" You may have to restart the app.";
} else {
reason = "Failed to save changes: " + reason +
" You may have to restart the app.";
}
console.log(error);
console.log(reason);
}
}
});
Please note this is my frist attempt at both a data class and VM. At the moment i am relying heavily on clearing the array ([]) and using notifySubscribers to make the array refresh :(
I bet you're missing an observable somewhere. I can't tell because you keep hopping from property to property whose definition is not shown.
For example, I don't know how you defined this.currentService.
I'm confused by this:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
Why is it a ko.computed? Why not just make it an observable array.
self.service = ko.observableArray();
// ... later replace the inner array in one step ...
self.service(doctorServices.getDoctorServices(
doctorList.viewModel.instance.currentDoctorID()));
I urge you to follow the observability trail, confident that your Breeze entity properties are indeed observable.
vm.selectedPrice = ko.dependentObservable(function () {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
}, vm);
vm is ur model on which u applied bindings , try this it will work.

Login via Titanium sync adapter for Rails

I want to be able to log in my Titanium app with credentials from my Rails app database.
In Titanium, I created the following model:
exports.definition = {
config: {
'adapter': {
'type': 'myAdapter',
'base_url': 'http://server:3000/api/users/'
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {
// custom functions
login: function() {
this.sync("login", this);
}
});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {});
return Collection;
}
}
The sync adapter I created, from the Twitter example given in the official Appcelerator doc:
// Global URL variable
var BASE_URL = 'http://server:3000/api/';
// Override the Backbone.sync method with the following
module.exports.sync = function(method, model, options) {
var payload = model.toJSON();
var error;
switch(method) {
// custom cases
case 'login':
http_request('POST', BASE_URL + 'login', payload, callback);
break;
// This case is called by the Model.fetch and Collection.fetch methods to retrieve data.
case 'read':
// Use the idAttribute property in case the model ID is set to something else besides 'id'
if (payload[model.idAttribute]) {
// If we have an ID, fetch only one tweet
http_request('GET', BASE_URL + '', {
id : payload[model.idAttribute]
}, callback);
} else {
// if not, fetch as many as twitter will allow us
http_request('GET', BASE_URL + '', null, callback);
}
break;
// This case is called by the Model.save and Collection.create methods
// to a initialize model if the IDs are not set.
// For example, Model.save({text: 'Hola, Mundo'})
// or Collection.create({text: 'Hola, Mundo'}) executes this code.
case 'create':
if (payload.text) {
http_request('POST', BASE_URL + 'update.json', {
status : payload.text
}, callback);
} else {
error = 'ERROR: Cannot create tweet without a status!';
}
break;
// This case is called by the Model.destroy method to delete the model from storage.
case 'delete':
if (payload[model.idAttribute]) {
// Twitter uses a POST method to remove a tweet rather than the DELETE method.
http_request('POST', BASE_URL + 'destroy/' + payload[model.idAttribute] + '.json', null, callback);
} else {
error = 'ERROR: Model does not have an ID!';
}
break;
// This case is called by the Model.save and Collection.create methods
// to update a model if they have IDs set.
case 'update':
// Twitter does not have a call to change a tweet.
error = 'ERROR: Update method is not implemented!';
break;
default :
error = 'ERROR: Sync method not recognized!';
};
if (error) {
options.error(model, error, options);
Ti.API.error(error);
model.trigger('error');
}
// Simple default callback function for HTTP request operations.
function callback(success, response, error) {
res = JSON.parse(response);
console.log("response |" + response);
console.log("res |" + res);
console.log("res str |" + JSON.stringify(res))
console.log("options |" + options);
if (success) {
// Calls the default Backbone success callback
// and invokes a custom callback if options.success was defined.
options.success(res, JSON.stringify(res), options);
}
else {
// res.errors is an object returned by the Twitter server
var err = res.errors[0].message || error;
Ti.API.error('ERROR: ' + err);
// Calls the default Backbone error callback
// and invokes a custom callback if options.error was defined.
options.error(model, err, options);
model.trigger('error');
}
};
};
// Helper function for creating an HTTP request
function http_request(method, url, payload, callback) {
// Generates the OAuth header - code not included
var header;
//= generate_header(method, url, payload);
var client = Ti.Network.createHTTPClient({
onload : function(e) {
if (callback)
callback(true, this.responseText, null);
},
onerror : function(e) {
if (callback)
callback(false, this.responseText, e.error);
},
timeout : 5000
});
// Payload data needs to be included for the OAuth header generation,
// but for GET methods, the payload data is sent as a query string
// and needs to be appended to the base URL
if (method == 'GET' && payload) {
var values = [];
for (var key in payload) {
values.push(key + '=' + payload[key]);
}
url = url + '?' + values.join('&');
payload = null;
}
client.open(method, url);
//client.setRequestHeader('Authorization', header);
client.send(payload);
};
// Perform some actions before creating the Model class
module.exports.beforeModelCreate = function(config, name) {
config = config || {};
// If there is a base_url defined in the model file, use it
if (config.adapter.base_url) {
BASE_URL = config.adapter.base_url;
}
return config;
};
// Perform some actions after creating the Model class
module.exports.afterModelCreate = function(Model, name) {
// Nothing to do
};
The rails app responds with JSON and it works but the problem is in the callback function:
[INFO] : response |{"email":"huhu#gmail.com","password":null}
[ERROR] : Script Error {
[INFO] : res |[object Object]
[INFO] : res str |{"email":"huhu#gmail.com","password":null}
[INFO] : options |[object Object]
[ERROR] : backtrace = "#0 () at file://localhost/.../xxx.app/alloy/sync/myAdapter.js:4";
[ERROR] : line = 30;
[ERROR] : message = "'undefined' is not a function (evaluating 'options.success(res, JSON.stringify(res), options)')";
[ERROR] : name = TypeError;
[ERROR] : sourceId = 216868480;
[ERROR] : sourceURL = "file://localhost/.../xxx.app/alloy/sync/myAdapter.js";
[ERROR] : }
So, does somebody have an idea why options is undefined...? Note that if I call the fetch method to get users using the read method, it works. By the way, is there a good way in authenticating somebody?
you are not passing in an options parameter in your model try making a change like this below
this.sync("login", this, {
success: function() {},
error: function(){}
});

Resources