Look up / join data with AngularFire - firebase

I've got a question on how to combine data from different Firebase database nodes before displaying it on the frontend. I've got a Firebase database with the following structure. (I'm new to a nosql setup, so this looks more relational):
{
"agents" : {
"-KPCmnwzjd8CeSdrU3As" : {
"contactNumber" : "12345",
"name" : "aaa"
},
"-KPCmw6dKuopDlsMVOlU" : {
"contactNumber" : "123",
"name" : "bbb"
},
"-KPCoWcLecpchcFV-vh_" : {
"contactNumber" : "123",
"name" : "ccc"
},
"-KPROMhPatLjVxMdvfLf" : {
"contactNumber" : "256342",
"name" : "blah"
},
"-KPWIFl5qp5FvAeC3YhG" : {
"contactNumber" : "123",
"name" : "eee"
}
},
"listings" : {
"-KPWKTvW3GzFEIT2hUNU" : {
"agent" : "-KPCoWcLecpchcFV-vh_",
"description" : "third",
"reference" : "REF1"
}
}
}
I'm using Firebase SDK 3.2.0 and AngularFire 2.0.1. In my Angular app I am able to get the list of listings, and for each one look up the agent information. The reason I'm not storing the agent info with the listing is I want the ability to update the agent and the change should reflect on all listings. I don't want to have to go and update all listings if the agent telephone number changes (as an example).
In my controller I have the following:
// get the listings
var listingsRef = firebase.database().ref().child('listings');
vm.listings = $firebaseArray(listingsRef);
// this will move to my ui-router as a resolve but for simplicity's sake
// I added it here...
vm.listings.$loaded().then(function(data){
// loop through the listings...
data.forEach(function(listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
}
});
});
Right now the data is displaying correctly on the frontend. There is a slight delay with the agent data showing because of the need of the getAgent promise to resolve.
My questions are:
Is this the correct way of getting the agent data? Should I be looping through the listings and for each query the agent data? How do I wait / keep track of all of the getAgents to resolve?
Any help would be appreciated.
Thanks.

I've structured my data similarly. If you want to wait for all the getAgents to resolve you can use $q.all. I'm not entirely sure what your AgentFactory.getAgent is returning, but let's assume it's a $firebaseObject. If that's the case inject $q and then do the following:
vm.listings.$loaded().then(function (data) {
// loop through the listings...
var promises = [];
data.forEach(function (listing) {
if (listing.agent) {
// get the agent for the listing
listing.agent = AgentFactory.getAgent(listing.agent);
promises.push(listing.agent.$loaded());
}
});
return $q.all(promises);
}).then(function (agents) {
//all agents are loaded
});

Related

How to search in Firebase this very large list of users

I try to create a people of the world model in Firebase. This means that somewhere I need to put 500 miljon+ records like this Firebase json:
"USER" : {
"HnhLyXRxUINmlltKOfxx2QBYiQ53" : { // FireBase user ids
"FRIENDLYS" : {
"-KWrFrMx1yQe0jhyA7HP" : { // Push ids
"animal" : "dog"
},
"-KWrG7EsLkZvASjpcW3A" : {
"animal" : "cat"
},
"-KWrG9hbQxrZwdfjqbuQ" : {
"animal" : "horse"
}
},
"email" : "someemail#gmail.com",
"lastName" : "Doe",
"firstName" : "John",
"googleId" : "10086464927329323e2326"
},
"HnhLyXHKJH67677GJKMnbBYiQ53" : { // FireBase user ids
"FRIENDLYS" : {
"-KWrFrMdjnjs982s7HP" : { // Push ids
"animal" : "ant"
....
}
I want to search for one USER like this:
firebase.child("USER/HnhLyXRxUINmlltKOfxx2QBYiQ53").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
The question is if this is a good approach to keep building on, especially Is it feasible to have a "USER" object this big when searching like i do?
What can I expect?
Your code is not searching for a user. You're constructing a direct reference to a child node, which operation itself does not have any performance implications. Looking up a key directly is always fast, so the number of users will not negatively affect your application.
However, storing "FRIENDLYS" [sic] under the user does not scale. Loading a single user node will get slower with each extra child put there. You should store them separately under a different root node and load them only if you actually need them.
See also the Structure Your Data section of the Realtime Database guide.

Synchronizing data across collections in meteor

I am using meteor to keep track of a person's progress as they progress through quiz questions. I want to return a random question from quizzes that matches the current question they are on. My trouble is that I have two methods, one creates a document to keep track of their progress through the whole quiz, and another creates a document to keep track of a particular question as well as documents that match back to the answers. I need to have the "questionId" set in one method call equal the "currentQuestionId" in the other.
My problem is that in trying to synchronize the currentquestionId in my responseToLectures, with the question that it actually picks, does not quite work in my current code because the methods are asynchronous, but I am calling them in a synchronous manner. Could I get help for how to refactor this code to be more meteor-like? The funny thing I notice is that if I put console.logs in the inner most "if" block, I will see that they get called >5 times before the page loads, I guess that I expect that as soon as it gets returned, that it would stop running the block and the next time that it gets loaded, it would just return the right question immediately. Thanks!
Question: function(){
var responsesToLectures = ResponsesToLectures.findOne({});
if (responsesToLectures != null){
if(responsesToLectures.currentProblemId == null){
var questionCount = Questions.find({level: responsesToLectures.currentLevel}).count();
if(questionCount != null){
var randomNumber = Math.floor((Math.random() * questionCount));
var randomQuestion = Questions.findOne({level: responsesToLectures.currentLevel}, {skip: randomNumber});
if (randomQuestion != null) {
Meteor.call('setRandomQuestion', randomQuestion._id, randomQuestion.lectureId);
return randomQuestion;
}
}
} else {
return Questions.findOne({_id : responsesToLectures.currentProblemId});
}
}
},
The issue is that the Question.findOne which gets returned from this a different id from the one which I set using 'setRandom' question. What is the proper way to retrieve values from documents in collections so that I can shuttle them into documents from other collections?
This is the mongo db to show you what I mean:
meteor:PRIMARY> db.responses.find();
{ "_id" : "zTTF7GrnuRqT4JNck", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "yHQzekpge486CwqLJ" }
{ "_id" : "47d2hhw7okLq2SXhm", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "WyyKfxzbinjxgrujd"}
{ "_id" : "ZqQu3TDcdMeo2SFBR", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "MES2Aj8PTNzMmthyh"}
{ "_id" : "dHtyKSxDccXvBTjnD", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6", "answerId" : "tPcCSjS8qhL7oebWZ" }
meteor:PRIMARY> db.responsesToLectures.find();
{ "_id" : "TohrY5tHK2DJ2ngWb", "userId" : "hneHgetL9oYaL2iWA", "lectureId" : "pZKuD2LnBh73wPTtN", "currentLevel" : 1, "currentQuestionId" : "FZBQu5wQq2bA2e6SB" }
meteor:PRIMARY> db.scores.find();
{ "_id" : "9HhC8CBG4JJTxcR8m", "userId" : "hneHgetL9oYaL2iWA", "questionId" : "ZSYJXn59ykA9QrTv6" }
For those who encounter this type of issue in the future, I was able to use collection hooks to synchronize the Ids across multiple collections. This worked for me!
ResponsesToLectures.before.insert(function(userId, doc){
var questionCount = Questions.find({level: doc.currentLevel}).count();
if(questionCount != null){
var randomNumber = Math.floor((Math.random() * questionCount));
var randomQuestion = Questions.findOne({level: doc.currentLevel}, {skip: randomNumber});
if (randomQuestion != null) {
doc.currentQuestionId = randomQuestion._id;
}
}
});

Meteor private messaging between users

Right now I have a working messaging system developed in Meteor where users can send private messages to each other.
The server looks like this:
// .. lot of code
Meteor.publish("privateMessages", function () {
return PMs.find({ to: this.userId });
});
PMs.allow({
insert: function(user, obj) {
obj.from = user;
obj.to = Meteor.users.findOne({ username: obj.to })._id;
obj.read = false;
obj.date = new Date();
return true;
}
});
// .. other code
When the user subscribes to privateMessages, he gets a mongo object that looks like this:
{ "to" : "LStjrAzn8rzWp9kbr", "subject" : "test", "message" : "This is a test", "read" : false, "date" : ISODate("2014-07-05T13:37:20.559Z"), "from" : "sXEre4w2y55SH8Rtv", "_id" : "XBmu6DWk4q9srdCC2" }
How can I change the object to return the username instead of the user id?
You need to do so in a way similar to how you changed username to _id. You can create a utility function:
var usernameById = function(_id) {
var user = Meteor.users.findOne(_id);
return user && user.username;
};
Edit:
If you don't want to poll minimongo for each message, just include username instead of _id inside your message object. Since username is unique, they will be enough.
If in your app you allow users to change username, it might be a good idea to also keep the _id for the record.
In one of larger apps I've been working with we kept user's _id in the model (to create links to profile etc.), as well as cached his profile.name (for display purposes).
I suggest adding the collection helpers package from atmosphere. Then create a helper for PMs called toUser that returns the appropriate user.
Then you can get the name using message.user.name.

Different URLs for Model and Collection in Titanium Alloy

Env: Titanium 3.1.3, Alloy 1.2.2.
I'm using the following adapter for persistence on the models/collections: https://github.com/viezel/napp.alloy.adapter.restapi
I have an API that has a different URL structure for a collection than it does a single model. Consider the following:
To get a single record: [GET] /files/:id
To get all the files for a user: [GET] /users/:id/files
I have the following schema for files.js:
exports.definition = {
config: {
"URL": "https://my.api.here/files",
//"debug": 1,
"adapter": {
"type": "restapi",
"collection_name": "files",
"idAttribute": "id"
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
initialize: function(){
this.url = "http://my.api.here/users/"+this.user_id+"/files";
}
});
return Collection;
}
}
What I'm trying to do in the above is override the collection initialize method to change the URL structure for the collection. I then call this accordingly:
var currentUserFiles = Alloy.createCollection("files", {user_id:"12345"});
currentUserFiles.fetch({
success: function(files){
console.log("Woo! Got the user's files!");
console.log(JSON.stringify(files.models));
},
error: function(){
console.log("Nope");
}
});
This doesn't work. The fetch() method just continues to try to call /files. I've tried setting url as a property on the collection after it's created, that also don't work.
Ideally, I'd like to do this for both local instances as well as the singleton version of the collection.
So - the question is: can I utilize a different URL for a collection than I do for a model? Obviously, I don't want to just call /files and sort/filter client-side - that'd be a nightmare with a lot of records. What am I missing here?
It's a bit late but for anyone else that comes across this. I problem is where/how the url is specified for model and collection. The model needs a specific id (eg: primary key) passed into it because the model can only be one object. If you need more than one object, then use the collection. Hope this helps :)
extendModel : function(Model) {
_.extend(Model.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files/"+ FILE_ID
},
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
url : function() {
return "http://my.api.here/users/"+this.user_id+"/files"
},
});
},

Simple DB storage using Firebase

I been trying to use Firebase to set up a simple DB to store usernames.
Right now am running a localhost page with a input field and a button to submit to Firebase.
My Firebase code is:
var myRootRef = new Firebase('https://myappname.firebaseio.com/');
var childRef = myRootRef.child('users');
Then inside my click event I do:
childRef.push(_user);
This works but I end up with the following structure on Firebase:
{
"users" : {
"-J1aJh8xQCgYU23m5qWc" : "sdfsd",
"-J1aJQ1g5dw25SCGTLlq" : "ad",
"-J1aJPZET6j7Kn8-nIZR" : "ad",
"-J1aJg_ZchLeEVdnJnTb" : "adam",
"-J1aJQrC2T5CfT0bNgeY" : "aaa",
"-J1aJQTguxhvrMqcI4uU" : "dddd"
}
}
When what I want is:
{
"users" : {
"user" : "sdfsd",
"user" : "ad",
"user" : "ad",
"user" : "adam",
"user" : "aaa",
"user" : "dddd"
}
}
Reading the Docs it looks like I need to use setPriority but passing a number overwrites my previous data... How does one go about setting up saving like I desire? Thank you.
If you want human friendly names, you'll need to manually generate them. One way to do this is to create a counter, for example, in /lastUser. Every time you want to create a new user, you'll increment this counter using transaction, and then create the user by using set instead of push.
var myRootRef = new Firebase('https://myappname.firebaseio.com/');
var childRef = myRootRef.child('users');
var counterRef = myRootRef.child('lastUser');
counterRef.transaction(function(current) {
return current + 1;
}, function(err, committed, snapshot) {
if (!err && committed) {
childRef.child("user" + snapshot.val()).set("userdata");
}
});
For a more full fledged example check out https://gist.github.com/anantn/4323981, you can use the same approach to generate human friendly userIDs as well.

Resources