I have a table called friends with fields
userid and friendid.
I want to query the database to find a user's friends. This is working fine by using the following code:
Parse.Cloud.define("searchfriend", function(request, response) {
var query = new Parse.Query("friends");
query.equalTo("player", request.params.myid);
query.find({
success: function(results) {
var listfreundids = [];
for (var i = 0; i < results.length; ++i) {
listfreundids[i] = results[i].get("friend");;
}
response.success(listfreundids);
},
error: function() {
response.error("error");
}
});
});
Now I have the problem to find the username matching the friendid because i cannot use a 2nd query within the for loop to query the user database...
Using promises you can split this up into several separate parts. Promises are really awesome to use and really easy to create your own promises too.
What I would do is split this up into a query that finds the friend ids and then a query that finds the friends...
Parse.Cloud.define("searchfriend", function(request, response) {
getFriendIDs(request.params.myid).then(function(friendIDs) {
return getFriendUserNames(friendIDs);
}).then(function(friends) {
response.success(friends);
}), function(error) {
response.error(error);
});
});
// function to get the IDs of friends
function getFriendIDs(myID) {
var promise = new Parse.Promise();
var query = new Parse.Query("friends");
query.equalTo("player", myID);
query.find().then(function(friendIDs) {
promise.resolve(friendIDs);
}, function(error) {
promise.reject(error);
});
return promise;
}
// function to get the friends from a list of IDs
function getFriendUserNames(friendIDs) {
var promise = new Parse.Promise();
var query = new Parse.Query("_User");
query.containedIn("id", friendIDs);
query.find().then(function(friends) {
// here I am just returning the array of friends
// but you can pull the names out if you want.
promise.resolve(friends);
}, function(error) {
promise.reject(error);
});
return promise;
}
You could always user a matches query too...
// function to get friends
function getFriends(myID) {
var promise = new Parse.Promise();
var friendQuery = new Parse.Query("friends");
friendQuery.equalTo("player", myID);
var userQuery = new Parse.Query("User");
userQuery.matchesKeyInQuery("ID", "friendID", friendQuery);
userQuery.find().then(function(friends) {
promise.resolve(friends);
}, function(error) {
promise.reject(error);
});
return promise;
}
This will perform a joined query where it gets the friend IDs first and then uses the friend ID to get the user and returns the user object.
Also, use promises. They are much easier to work with and can be pulled apart into separate working units.
Of course, I have no idea if the syntax here is correct and what the correct names should be or even what your object model looks like but hopefully this can act as a guide.
You do not have to query for each friend id that you have found (inefficient). Instead, after getting the list of friend ids, you can query the user database via sending the list of friend ids with the query.containedIn constraints. This strategy will actually decrease the number of query count.Based on retrieved result you can get friend (previously found) information. One more thing to remember, call response success after the operations are executed.Hope this helps.
Regards.
Related
I have a query to a NoSQL database. Since in this type of database there are not many options for complex queries, I need to make two nested queries to other tables within a loop
created by the first query. Using promises, I decided to read the first nested query. However, the whole block is not "waiting" for the most internal query, and the results it brings
(from users) are not being counted. Here is the simplified code:
The block does the following: For each room (outermost loop), it reads all messages (first promise, from messageRef) and, for each message, takes the user data (second promise, from
usersRef). The first nested query is ok (brings up all messages). However, the user data for each message is not being loaded (in this case, I just put the name, for simplicity).
The messageRef and usersRef objects return promises that are stored in the promises array. At the end, when all are finalized (Promise.all), it returns the array with all the data.
Each "obj" object has the data of each room. In obj.messages, it has the array of message objects and, at each obj.messages, the user data.
roomRef.on('value', function(data){
var promises = [],
threads = [],
counter = 0;
$.each(data.val(), function(id, obj){
obj.id = id;
obj.messages = [];
var promise = messageRef.child(id).on('child_added', function(data){
var msgobj = data.val();
obj.messages.push(msgobj);
return usersRef.orderByChild('login').equalTo(msgobj.createdBy.login).once('child_added', function(data){
msgobj.createdBy.name = data.val().name;
});
});
promises.push(promise);
threads[counter] = obj;
counter += 1;
});
Promise.all(promises).then(function(){
console.log(threads) // array with all data
});
});
Thanks for your help.
If you need to query then use query not a event callback child_added.
child_added callback isn't return promise. It triggered when data added. change them to query that return promise.
roomRef.on('value', function(data) {
var promises = [],
$.each(data.val(), function(id, obj) {
obj.id = id
obj.messages = []
var promise = new Promise(resolve => {
messageRef.child(id).on('child_added', function(data) {
var msgobj = data.val()
obj.messages.push(msgobj)
usersRef
.orderByChild('login')
.equalTo(msgobj.createdBy.login)
.once('child_added', function(data) {
msgobj.createdBy.name = data.val().name
resolve(msgobj)
})
})
})
promises.push(promise)
})
Promise.all(promises).then(function(threads) {
console.log(threads) // array with all data
})
})
I have a function that returns a BehaviorSubject but when I try to use the data I get back from the function I need to use it once all the data is back, is there a way to know when the BehaviorSubject is done pulling all the data?
I tried using .finally but it never gets called. Here is the code I'm using.
getData() {
let guideList = '';
this.getChildren(event.node)
.subscribe(
function(data) {
console.log('here');
guideList = data.join(',');
},
function(err) {
console.log('error');
},
function() {
console.log('done');
console.log(guideList);
}
);
}
getChildren(node: TreeNode) {
const nodeIds$ = new BehaviorSubject([]);
//doForAll is a promise
node.doForAll((data) => {
nodeIds$.next(nodeIds$.getValue().concat(data.id));
});
return nodeIds$;
}
Attached is a screen shot of the console.log
Easiest way is to just collect all the data in the array and only call next once the data is all collected. Even better: don't use a subject at all. It is very rare that one ever needs to create a subject. Often people use Subjects when instead they should be using a more streamlined observable factory method or operator:
getChildren(node: TreeNode) {
return Observable.defer(() => {
const result = [];
return node.doForAll(d => result.push(d.id)).then(() => result);
});
}
I am trying to fill in an ItemView in Marionette with the combined results of 2 API requests.
this.standings = App.request('collection:currentStandings');
this.userInfo = App.request('model:userInfo');
this.standings.each(function(s) {
if (s.currentUser) {
s.set('alias', this.userInfo.alias);
s.set('imageURL', this.userInfo.imageURL);
}
});
userInfoView = new LeagueBar.UserInfo({ collection: this.standings });
The problem is, the combination never happens because the requests have not been fulfilled before I try to combine them.
I know I probably need to add a promise for each request, but I haven't been able to find a clean way to do it. I could make 'collection:currentStandings' and 'model:userInfo' return promises, however, they are currently used in many other parts of the code, so I would have to go back and add .then()s and .done()s all over the code base where they weren't required before.
Any ideas or suggestions?
EDIT:
I have currently solved this in a less-than-ideal way: I created a template/view for the alias and a template/view for the imageURL and kept the template/view for the standings info. This doesn't seem like the best way and I'm interested to know the right way to solve this problem.
here are the two requests I am trying to combine:
Models.CurrentStandings = App.Collection.extend({
model: Models.PlayerStandings,
url: function() { return 'leagues/' + App.state.currentLeague + '/standings'; },
parse: function(standings) {
return _.map(standings, function(s) {
if (s.memberId == App.user.id)
s.currentUser = true;
return s;
});
}
});
App.reqres.setHandler('collection:currentStandings', function() {
weekStandings = new Models.CurrentStandings();
weekStandings.fetch({ success: function(data){ console.log(data); }});
return weekStandings;
});
Models.UserInfo = App.Model.extend({
url: 'users/me'
});
App.reqres.setHandler('model:userInfo', function(options) {
myuser = new Models.UserInfo();
myuser.fetch(options);
return myuser;
});
There are 2 solutions which based on your dependencies among views can be selected:
You can create views which are handling 'change' event of Models.UserInfo and when the data is ready (Change/Reset event raised) re-render the content. It is probably your solution.
If you are looking for a solution which should not create instance of LeageBar.UserInfo until both Models.CurrentStanding and Models.UserInfo are ready, you have to return the result of fetch function, so you may remove calling fetch from setHandlers and use them as following:
this.standings = App.request('collection:currentStandings');
this.userInfo = App.request('model:userInfo');
var that=this;
that.standings.fetch().done(function(){
that.userInfo.fetch().done(function(){
that.standings.each(function(s) {
if (s.currentUser) {
//....
}
});
userInfoView = new LeagueBar.UserInfo({ collection: that.standings });
});
I am writing a cloud code function in parse. I am running a query and I am trying to figure out how to filter columns of the object I am querying for.
So, I am querying for user objects:
var userQuery = new Parse.Query(Parse.User);
userQuery.notEqualTo("username", username);
// so here how do I tell the query not return the values found in a column called "count"?
Thank you
You need to use .select()
Example below:
Parse.Cloud.define("testFunction", function(request, response) {
var userQuery = new Parse.Query(Parse.User);
userQuery.contains("objectId","7Ijsxy1P8J");
//Here you say which columns you want, it will filter all other ones
userQuery.select("objectId", "firstName", "lastName");
userQuery.first({
success: function(result) {
response.success(result);
},
error: function(error) {
response.error("Failed with error ", error);
}
});
});
Over there I get a PFUser by its objectId but filtering only some fields. Hope it helps.
Let's say I have a Users class and a Posts class, and each Post object has a User pointer. I also have a Comment class, and each Comment has a User pointer. Whenever I fetch a User, I want to fetch all the Posts and Comments that user has made in one network query. Compounding sub-queries won't work, since I have multiple data types. Ideally, I would like to have the Posts and Comments be properties of the retrieved User object.
Is there a way, in Cloud Code, to intercept object requests and modify them to have the appropriate properties before sending to the client?
You can't modify an object on retrieval, but you can write your own Cloud Function. In your Cloud Function you could take the ID of a user as a parameter, then create a composite object by combining the output of your 3 queries into one result.
You'll need to query:
User object
Posts for that user
Comments for that user
You could get the promise for each of those queries (find() etc return a promise object) and use Parse.Promise.when(userQueryPromise, postQueryPromise commentQueryPromise) to build your composite object and return it.
var _ = require('underscore');
Parse.Cloud.define('getUserWithPostsAndComments', function(request, response) {
var userId = request.params.userId;
var userPointer = new Parse.User();
userPointer.id = userId;
var userQuery = new Parse.Query(Parse.User);
var postQuery = new Parse.Query('Post');
postQuery.equalTo('user', userPointer);
var commentQuery = new Parse.Query('Comment');
commentQuery.equalTo('user', userPointer);
Parse.Promise.when(userQuery.get(userId), postQuery.find(), commentQuery.find())
.then(function(user, posts, comments) {
var composite = {
// User properties you want
name: user.get('username'),
email: user.get('email')
};
// Post properties you want
composite.posts = _.map(posts, function(post) {
return {
id: post.id,
title: post.get('title')
};
});
// Comment properties you want
composite.comments = _.map(comments, function(comment) {
return {
id: comment.id,
body: comment.get('bodyText')
};
});
// return the composite object
response.success(composite);
}, function(error) {
response.error(error);
});
});
Alternatively the above then block could just return the raw results, e.g.:
.then(function(user, posts, comments) {
return {
user: user,
posts: posts,
comments: comments
};
}, function(error) {
// etc...