how to do joins on Firebase tables - javascript

I am trying to get data from two tables like (fetch all users and their details)
tableOne.on('users', function (snapshot) {
userId = snapshot.val().userId; // line 1 (results like 1,2,3,4,5,6)
anotherTable.child('userdetails').child(userId).once('value', function(mediaSnap) {
// result // line 2
});
});
but the problem is line 1 executes first for the 6 times and then line 2 that n times resulting in everytime looking for 'where user id is - 6'...isn't joins supported in Firebase?
Any help is apreciated

Your code snippet has a nasty side-effect:
var userId;
tableOne.on('value', function (snapshot) {
userId = snapshot.val().userId; // line 1 (results like 1,2,3,4,5,6)
anotherTable.child('userdetails').child(userId).once('value', function(mediaSnap) {
console.log(userId + ":" + mediaSnap.val().name);
});
});
You're not declaring userId as a variable, which means that it becomes a global variable in JavaScript. And since the callback function executes asynchronously, there is a good chance that the global values will have changed by the time you need it.
The solution is simply to make userId a local variable of the callback function:
tableOne.on('value', function (snapshot) {
var userId = snapshot.val().userId; // line 1 (results like 1,2,3,4,5,6)
anotherTable.child('userdetails').child(userId).once('value', function(mediaSnap) {
console.log(userId + ":" + mediaSnap.val().name);
});
});
This will ensure that each value of userId is captured inside the function.

Solution for list join:
tableOne.orderByKey().on("value", function (snapshot) {
//console.log(snapshot.val());
snapshot.forEach(function (data) {
tableTwo.once('value').then(function (info) {
info = info.val();
});
});
});

Related

How do I reject writing value to Firebase if have the same value in my DB? (Javascript)

I'm creating my custom order id with auto-increment generator function for my project. I will state my question here, if you want to know the whole story please read below.
As written in the title, I need a way to reject my set to Firebase and it has to be done in 1 query. Currently, it will write my orderID to Firebase without rejecting it. But I need to reject if there is the same ID in the table.
The short version of my code will be posted here, the whole function will be posted below.
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
The story,
I'm creating my own custom order id with auto-increment generator function for my project. The problem is that if multiple users creating order at the same time, it will generate the same id. Yes, I can use transaction() to solve the problem but I have no idea how to use it. Therefore, I have created my own version of the "transaction". With my method, I am able to prevent duplicates id unless 2 or more users create order within 1 second of gap. Or if anyone is kind enough to show me an example of how to write a transaction for my function, I thank you in advance.
The flow of the code is,
Get "currentMonth" and "orderIdCounter" from Firebase -> orderIdCounter +1 and update to Firebase -> start the process of generating order id -> Send the generated id to firebase -> If return success "order ID created", If not "got duplicate id" Re-run the whole process.
Below is the code for my order id generator function.
function createOrderID(orderCounterRef){
var childData = [];
var orderID;
//Get the Current Month and Order ID Counter from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
var currentMonth = childData[0];
var orderIDCounter = childData[1];
if (orderIDCounter !== undefined){
//Update orderIDCounter on Firebase.
//This is to prevent duplicate orderID when multiple users is creating order at the same time.
var IDCounter = parseInt(orderIDCounter) + 1;
//Set IDCounter to 3 digits
IDCounter = ('00' + IDCounter.toString()).slice(-3);
firebase.database().ref('orderCounter/orderIDCounter').set(IDCounter);
//Handle the process to generate Order ID. Return in YYMMxxx(auto increment) format.
orderID = handleCreateOrderID(currentMonth, (parseInt(orderIDCounter) - 1));
//Check if duplicate ID on firebase
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
return orderID;
}
My DB:
You should indeed use a transaction as you have mentioned in your question.
The following should do the trick:
//Declare a function that increment a counter in a transaction
function createOrderID() {
var orderIdRef = firebase.database().ref('orderId');
return orderIdRef.transaction(function(currentId) {
return currentId + 1;
});
}
//Call the asynchronous createOrderID() function
createOrderID().then(function(transactionResult) {
console.log(transactionResult.snapshot.val());
});
If you want to start the counter at a specific value, just create an orderId node in your database and assign a specific value to it, e.g; 1912000.
If you just want to start at 1, you don't need to create a node, it will be automatically created with the first call to the createOrderID() function.
Thank you, #samthecodingman & #Renaud Tarnec for your advice.
I took #samthecodingman's code and change a bit to fit my project. But I use generateOrderID() only to call the result and it works well. But you won't get any value with just the code. I call out another function (connectToFirebase) whenever users enter the page. I am not sure why it works or if this is the right way, but it works for me and that's good enough.
export function generateOrderID(){
var orderId;
var childData = [];
const orderCounterRef = firebase.database().ref('orderCounter/');
//Get the Current Month from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
//Check ID format YYMMXXX (XXX=auto_increment). Hanlde auto_increment for Year and Month
handleOrderIdFormat(childData[0], orderCounterRef)
//transaction
orderCounterRef.child('orderId').transaction(function(currentId) {
orderId = (currentId||0) +1;
return orderId;
}, function(err) {
if( err ) {
console.log(err)
}
});
return orderId;
}
export function connectToFirebase(){
//Connection Firebase Database
const orderCounterRef = firebase.database().ref('orderCounter/');
orderCounterRef.on('value', function(snap) { });
}

Incremential operation with Cloud Functions for Firebase

I am trying to create a function for my database using Cloud Functions for Firebase. The purpose of the function is to listen to write events on the attend table and based on the object written to identify the event and increment the usersAttending on the event object.
This is my function so far.
//listens to write on attendObjects (when a user is attending an event), and increments attending users for event
exports.listenAttendingEvents = functions.database.ref('/attend/{pushId}').onWrite(event => {
//get attendObj -> parsed JSON by javascript interpreter
const attentObj = event.data.val();
const attendId = attentObj['attendId'];
const pathToAttendees = '/attends' + '/' + attendId;
// Attach an asynchronous callback to read the data at our posts reference
admin.database().ref(pathToAttendees).on("value", function(snapshot) {
console.log(snapshot.val());
const obj = snapshot.val();
var nrAttending = obj['attending'];
nrAttending = Number(snapshot.val());
return admin.database().ref(pathToAttendees + '/attending').transaction(function (nrAttending) {
return (nrAttending || 0) + 1;
});
});
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
return errorObject
});
The problems as it seems is that the event object doesn't get retrieved. The function seems to finish before that with the status ok
The problem was that I was not having a promise for my top-level function. This caused Google Cloud Functions to kill it before the operation was complete.
Adding a promise solved my problem
admin.database().ref(pathToAttendees).once("value").then( function(snapshot) {

Socket.io - Variables Not Updating Outside socket.on

I am working with Socket.io and MongoDB. When I first send my socket.emit to the server, the server takes in the parameter and returns with my desired output. When I reach the socket.on on my client.js the OrderID is shown to the console. Yet when I exit the socket.on method block, the GenOrderID variable becomes undefined.
My question is: why are variables that were created before the socket.on, no longer accessible outside it.
Here is the client.js I am working with:
// Create order array
var Order = [];
// Create GeneratedOrderID variable
var GenOrderID;
// Get School ID from cookie
var SchoolID = getCookie("SchID");
// Generate OrderID
socket.emit('GenerateOrderID', SchoolID);
socket.on('GenerateOrderID', function(GeneratedOrderID) {
console.log("OrderID sent from server is: " + GeneratedOrderID); // This returns the desired number
GenOrderID = GeneratedOrderID;
});
Order[0] = GenOrderID;
console.log("GenOrderID is: " + GenOrderID); // This returns undefined
console.log("Order[0] is: " + Order[0]); // This returns undefined
Here is the server.js I am working with:
socket.on('GenerateOrderID', function(PassSchoolID) {
// Connect to database
MongoClient.connect('mongodb://localhost:27017/Orders', function(err, db) {
// Handle errors
assert.equal(null, err);
console.log("Begin creation of OrderID");
db.collection('Orders').find({SchoolID: PassSchoolID}).sort({amount: -1}).limit(1).forEach(function (result) {
var GeneratedOrderID = parseInt(result.OrderID);
GeneratedOrderID++;
console.log("The server says the OrderID is: " + GeneratedOrderID); // This returns the desired number
// Return to client
socket.emit('GenerateOrderID', GeneratedOrderID);
});
});
});
By placing the console.log() inside the socket.on I was able to have it work properly.
// Create order array var
Order = [];
// Create GeneratedOrderID variable
var GenOrderID;
// Get School ID from cookie
var SchoolID = getCookie("SchID");
// Generate OrderID
socket.emit('GenerateOrderID', SchoolID);
socket.on('GenerateOrderID', function(GeneratedOrderID) {
console.log("OrderID sent from server is: " + GeneratedOrderID);
GenOrderID = GeneratedOrderID;
Order[0] = GenOrderID;
console.log("GenOrderID is: " + GenOrderID);
console.log("Order[0] is: " + Order[0]);
});
You're setting the value of GenOrderID inside a callback function, which is only executed once a GenerateOrderId event has occurred. The code inside of a callback function doesn't run until that function is called.
For example:
function run(f) {
// call `f` after 1 second
setTimeout(f, 1000);
};
var foo;
run(function() {
foo = 'bar';
console.log('in callback:', foo);
});
console.log('before callback:', foo);
// output:
// before callback: undefined
// in callback: 'bar'
At some point in the future, foo will equal 'bar', but you can only know this is the case after your callback has been called. And you can only know that your callback has been called from inside it.

Insert document loop - RangeError: Maximum call stack size exceeded

I am literally giving my first steps with node and mongodb and I have recently hit this RangeError wall.
Here's what I am trying to do, I have a file that contains a list of countries that I would like to add to my mongo db. This would be part of my "seed" mechanism to get the app running.
I load the json and then I iterate through the collection of objects and add them one by one to the 'Countries' collection.
However, everytime I run the code, I get a "RangeError: Maximum call stack size exceeded".
I have googled around but none of the suggested solutions seem to apply for me.
My guess is there is something wrong with my insertCountry function...
Anyways, here's my code:
var mongoose = require('mongoose');
var countries = require('./seed/countries.json');
// mongodb
var Country = mongoose.Schema({
name: String,
code: String,
extra: [Extra]
});
var Extra = mongoose.Schema({
exampleField: Boolean,
anotherField: Boolean
});
var mCountry = mongoose.model('Countries', Country);
var mExtra = mongoose.model('Extras', Extra);
// do connection
mongoose.connect('...');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error'));
db.once('open', function callback() {
});
// async function
var insertCountry = function(document, callback) {
db.model('Countries').count({code: document.code}, function (err, count) {
if (count < 1) {
db.collection('Countries').insert(document, function (err, result) {
if (!err) {
console.log('country ' + document.name + ' added');
}
else {
console.log('- [' + document.name + '] ' + err);
}
});
}
callback(null,document);
});
};
// doing countries
var Country = mongoose.model('Countries');
var Extras = mongoose.model('Extras');
for(i = 0; i < countries.length; i++)
{
nCountry = new Country();
nCountry.name = countries[i].name;
nCountry.code = countries[i].code;
nCountry.benefits = new Extras();
nCountry.benefits.exampleField = false;
nCountry.benefits.anotherField = false;
insertCountry(nCountry, function (err, value) {
console.log(value.name + ' added to collection (callback)');
});
}
I have been using some guides I have found to build this so this might not be optimal code. Any best pratices, standards, guides or tutorials you can share are most welcome!
Your callback is in the wrong place. It is not waiting for the insert operation to complete before you return from it's own callback. Altering your code:
var insertCountry = function(document, callback) {
db.model('Countries').count({code: document.code}, function (err, count) {
if (count < 1) {
db.collection('Countries').insert(document, function (err, result) {
if (!err) {
console.log('country ' + document.name + ' added');
}
else {
console.log('- [' + document.name + '] ' + err);
}
callback(null,document);
});
}
});
};
That is part of your problem, but it does not completely solve it. The other part is the loop which also does not wait for the wrapping function to complete before moving on. You want something like asyc.eachSeries in order to wait for inserts to complete before performing the next iteration. This is mostly why you are exceeding the call stack:
async.eachSeries(
countries,
function(current,callback) {
// make your nCountry object
insertCountry(nCountry,function(err,value) {
// do something, then
callback(err);
})
},
function(err) {
// called where done, err contains err where set
console.log( "done" );
}
);
There is really still and issue with the array, which must be reasonably large if you are exceeding the call stack limit. You probably should look at using event streams to process that rather that load everything in memory to the array.
Personally, if you were just trying not to insert duplicates for a field and had MongoDB 2.6 available I would just use the Bulk Operations API with "unordered operations" and allow non fatal failures on the duplicate keys. Coupled with the fact that bulk operations are sent in "batches" and not one at a time, this is much more efficient than checking for the presence on every request:
var Country = mongoose.Schema({
name: String,
code: { type: String, unique: true }, // define a unique index
extra: [Extra]
});
var insertCountries = function(countries,callback) {
var bulk = Country.collection.initializeUnorderedBulkOp();
var counter = 0;
async.eachSeries(
countries,
function(current,callback) {
// same object construction
bulk.insert(nCountry);
counter++;
// only send once every 1000
if ( counter % 1000 == 0 ) {
bulk.execute(function(err,result) {
// err should generally not be set
// but result would contain any duplicate errors
// along with other insert responses
// clear to result and callback
bulk = Country.collection.initializeUnorderedBulkOp();
callback();
});
} else {
callback();
}
},
function(err) {
// send anything still queued
if ( counter % 1000 != 0 )
bulk.execute(function(err,result) {
// same as before but no need to reset
callback(err);
});
}
);
};
mongoose.on("open",function(err,conn) {
insertCountries(countries,function(err) {
console.log("done");
});
});
Keeping in mind that unlike the methods implemented directly on the mongoose models, the native driver methods require that a connection is actually established before they can be called. Mongoose "queues" these up for you, but otherwise you need something to be sure the connection is actually open. The example of the "open" event is used here.
Take a look at event streams as well. If you are constructing an array large enough to cause a problem by missing callback execution then you probably should not be loading it all in memory from whatever your source is. Stream processing that source combined with an approach as shown above should provide efficient loading.

Add new element in existing object

I am using node.js.
I have to add new elements in the object before to send a response to client.
user.getMatch(req.user, function(err, match){
for( k=0; k<match.length; k++){
var userId = {
id : match[k].match_id
};
var user = new User(userId);
console.log('k: ' + k);
user.getUserInfo(function(err2, info){
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
});
}
var response = {
data : match
};
res.json(response);
});
I want to add an element "foo" from user.getUserInfo to the object "match" that was returned by user.getMatch. And then send all the data as response to the client.
But it got an error because "k" inside of user.getUserInfo is not equal to the "k" outside.
I do not know why the both "k" are not equal.
And how will I send a response to the client after performing the loop.
Thanks for your help!
Some problems here:
First, k is not defined so the k you're using is actually a global variable which is not what you want. You need to define it as 'var k'.
Second, the callback function you're passing to user.getUserInfo() is (probably) executed at some unknown time in the future. At this point your loop for (k ... has already finished so the the k variable already has a new value since the value that it had when you called user.getUserInfo(). And here's the tricky part: the code inside your callback function will use k's most recent value. It will not use the value that k had when the function was created.
You can solve this by adding a parameter to your callback function and binding k to it using the .bind method:
user.getMatch(req.user, function(err, match){
var k;
for(k=0; k < match.length; k++){
var userId = {
id : match[k].match_id
};
var user = new User(userId);
console.log('k: ' + k);
var callback = function(k, err2, info){
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
}.bind(null, k);
user.getUserInfo(callback);
}
var response = {
data: match
};
res.json(response);
});
Also, you'd be better off by using .forEach for iterating over an array:
user.getMatch(req.user, function(err, match){
match.forEach(function(curr) {
var userId = {
id : curr.match_id
};
var user = new User(userId);
user.getUserInfo(function(err2, info){
if(info){
curr.foo = info[0].foo;
}
}
});
var response = {
data: match
};
res.json(response);
});
Although Array.forEach can give you your current index in the iteration, this is no longer needed. simply use the curr value (which gives you the current element in the iteration).
Finally, I think the code here is likely to send the response before all user.getUserInfo() calls have been executed. To achieve that you need to know when all user.getUserInfo() have been completed. This can be achieved by adding a variable numLeft which is decremented each time we get a user info. when this variable reaches zero we know that all getUserInfo() have completed and it is therefore safe to send the response back.
user.getMatch(req.user, function(err, match) {
var numLeft = match.length;
match.forEach(function(curr) {
var user = new User({
id : curr.match_id
});
user.getUserInfo(function(err2, info){
if(info) {
curr.foo = info[0].foo;
}
--numLeft;
if (numLeft == 0)
res.json({ data: match });
}
});
});
When you say "k inside and outside" do you mean inside and outside ofuser.getUserInfo(function(err2, info){})?
I am not sure of your context however i can think of two things
Since the function "function(err2, info)" is a callback and is executed asynchronously the context/stack in which k is used within getUserInfo is completely different. So try to pass k while calling i.e.
user.getUserInfo(function(err2, info, k){}). This should work
Try to declare k i.e var k in the closure that you want it to be used
Updating for another part of question
"But I got another issue.. it sends a response to client before it adds the "foo" element. So in the response to client, it only sends the object from "match" without the "foo" element."
That is again because ur code inside get user info gets executed asynchronously.
For this you need to keep a global flag or try to send the response from within getUserInfo
i.e.
var mathLen = match.length;
user.getUserInfo(function(err2, info,k,mathLen)
{
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
if(k==mathLen)
{
var response = {
data : match
};
res.json(response);
}
});

Categories