Synchronizing data across collections in meteor - 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;
}
}
});

Related

firebase realtime database calling set with new properties

i am using firebase realtime DB with my ionic app and using angularfire2. I have data stored like:
"users" : {
"230A3lKQqWh0TczOGP8sbtMVpuF2" : {
"email" : "xxx#gmail.com",
"isAdmin" : false,
"name" : "Santosh Yadav",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"oSEYj0zrkhPCk9r7uwyOOkHcqe53" : {
"email" : "yyy#gmail.com",
"isAdmin" : true,
"name" : "Vik Kumar",
"pic" : "https://graph.facebook.com/xxx/picture"
},
"tl3uvseaBeWVYFCTwSngUqcSokX2" : {
"email" : "zzz#gmail.com",
"isAdmin" : false,
"name" : "Neeti Singhal",
"pic" : "https://graph.facebook.com/xxx/picture"
}
}
We ended up to store more user data like date of birth and date of aniversary and when we try to update it as below:
createUser(user:User){
console.log('start of create user')
var payload = {
"name": user.name,
"email": user.email,
"pic" : user.pic,
"dob" : user.dob,
"anivDate" : user.anivDate,
}
return this.db.object('/users/' + user.uid).set(payload).then(
(resp) => console.log("user created")
).catch(
(err) => console.log("issues creating user:" + err)
)
}
It fails with error:
ERROR Error: Reference.set failed: First argument contains undefined in property 'users.oSEYj0zrkhPCk9r7uwyOOkHcqe53.anivDate'
at validateFirebaseData (validation.js:113)
at validation.js:140
at forEach (obj.js:46)
I understand the error that it is unable to set a property anivDate that does not exist in my firebase realtime DB. This will fix if i manually add the property into the db manually. But that is not a practical fix. So, what is the solution here?
To define the fix: i am expecting to add a new property if that does not exist else just update it.
For a quick fix, use the || operator to set a default value on undefined elements. Usage:
var payload = {
"anivDate": user.anivDate || "1970-01-01",
...
};
This sets anivDate to user.anivDate if it exists, 1970-01-01 if it does not. (This works, but in my opinion it is better practice to not have a anivDate property on a user if no such value exists, and check for that value's existence when accessing the data.)
For a more scalable/reliable fix, use Firebase's realtime database triggers to add async validating or default values to your new columns.

How to implement user based security in the firebase database?

This is the database for example:
"Messages" : {
"Message1" : {
"Uid" : "sampleid1"
"Text" : "hi"
},
"Message2" : {
"Uid" : "sampleid2"
"Text" : " hello"
}
}
I want only those users to read the messages whose uid is equal to the Uid field of Message#.
The structure of database given in firebase documentation(i.e. using user id based messages in the database where the node of each message represents the uid of the user who sent the message) doesn't achieve the goal of my project as I need to know the uid of the user who sent the message each time any user sends a message.
Therefore, please suggest the rules that would help me achieve my task as mentioned in this question
Also, when I applied certain rules on the above structure of database, I couldn't read any data because 'firebase rules are not filters'.
Please ignore the syntax and format of json written in above example as it is just for reference
Please help!
Structure your data so:
"messages" : {
"<receiver_uid>" : {
"msg_1" : {
"text" : "Hello world...",
"uid" : "<sender_uid>"
}
// more msgs for this receiver ...
}
}
the rules should be something like
{
"rules" : {
"messages" : {
"$receiver" : {
".read" : "auth.uid == $receiver", // only receiver shall read
".write" : "auth != false" // any authenticated user can write
}
}
}
}

Look up / join data with AngularFire

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
});

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.

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