Only download Firebase snapshots that have child key - javascript

My data structure looks like this (removed unnecessary parts):
{
"threads" : {
"PUSHID" : {
"info" : {
"members" : {
"uid" : true,
"uid2" : true
}
}
}
}
}
I'm trying to write some javascript to pull snapshots of threads a user is in, but I can't figure out a way for it to work without pulling snapshots of each thread. This is my code now that pulls each thread snapshot.
firebase.database().ref('threads').on('child_added', function(snapshot) {
if (snapshot.hasChild('info/members/' + userUid)) {
// Display thread info
}
});
I tried to make a query with .orderByChild('info/members/' + userUid) and removing null snapshots, but I would have to add a .indexOn for each userUid which is obviously not practical.

Your current structure makes it easy/efficient to look up the users for a thread. But your use case is to look up the threads for a user. You'll need to augment your data model to allow the use-case:
{
"user_threads" : {
"uid": {
"PUSHID": true,
"PUSHID2": true
},
"uid2": {
"PUSHID": true,
"PUSHID3": true
}
}
}
And then read it with:
firebase.database().ref('user_threads/'+userUid).on('child_added', function(snapshot) {
...
});
Modifying/expanding your data model to match the use-cases of your app is quite common when using NoSQL databases.

Related

Firebase orderByValue().equalTo() is not a function (Node JS)

My database has the following structure:
"groupA" : {
"applications" : {
"applicationID_132" : {
"status" : "accepted"
},
"applicationID_423" : {
"status" : "declined"
},
"applicationID_562" : {
"status" : "accepted"
}
}
}
I am trying to retrieve the snapshot of applications with "accepted" status (to be able to skip fetching all applications) by:
return admin.database().ref('groupA').child('applications').orderByValue('status').equalTo('accepted')
.once('value')
.then(acceptedApplicationsSnapshot => {
Doesn't seem to work, tried orderBy and orderByValue as well. What might be missing or what is the better way to handle this case?
Thank you for any help!
Try using child.orderByChild().value()
In your case:
return admin.database().ref('groupA').child('applications').orderByChild('status').equalTo('accepted')
.once('value')
.then(acceptedApplicationsSnapshot => {
You could use orderByValue() as well, but first you would need to get the Reference which you want to order - function does not accept parameter.
How to use orderByValue()?
.child(..) returns a Reference in your case that is applications if you want to use orderByValue than you would need to get reference to 'status' before doing that, eg:
admin.database().ref('groupA').child('applications').child('status').orderByValue() and then you would apply one of the data filtering methods eg.
.equalTo('accepted')...

pouchDB query() bug when updating documents

Let's say I have these three documents:
{ "_id": "11111", "type": "template", "name": "person" }
{ "_id": "22222", "type": "template", "name": "place" }
{ "_id": "33333", "type": "template", "name": "thing" }
I have a cloud database and then I have a device with pouchDB syncing from that database.
These are the steps that I do:
I sync both databases together. So now I have the most recent versions of this document on my device.
I run the below query and I get back all three templates like so:
Code
var template_obj = {};
return device_db.query('filters/templates')
.then((templates) => {
for (let t of templates.rows) templates_obj[t.id] = true;
return templates_obj;
});
filters/templates
function (doc) {
if(doc.type == "template")
emit(doc._id);
}
return
{ "11111": true, "22222": true, "33333": true }
I update template: person on cloud. And then I update it again. So 2 revisions have gone by without syncing to my device.
I sync with my device.
Now when I run the same query and I only get back the document I edited. Which is weird because I haven't touched any of the other documents. The same view returns the expected results on the cloud but not on the device.
return
{"11111": true}
If I do the following code however, all templates come back as normal and the same _rev from the cloud show up on the device. Meaning the sync was successful and view is getting confused.
new code
return device_db.allDocs({conflicts: true})
.then((data) => {
for (let d of data.rows) {
if(d.doc.type == "template") {
templates_obj[d.doc._id] = true;
}
}
return templates_obj;
}).catch((err) => {
console.log(JSON.stringify(err));
})
I'm starting to believe this is a bug because if I destroy my database and do these steps again, I can reproduce this issue.
After realizing you are using React Native, I think this actually has to do with PouchDB in React Native, and it's indeed a bug. There are several reports of that behavior:
https://github.com/pouchdb/pouchdb/issues/7219
https://github.com/pouchdb/pouchdb/issues/7188
https://github.com/pouchdb/pouchdb/issues/7293
[edit: Seems to be a bug in PouchDB with React Native. I leave this answer because it might be helpful in other ways.]
I suspect it's some side effect with the global variable template_obj you are using. Try to console.log(templates.rows) directly instead of storing it in a variable in the top scope, or use Array.reduce() to avoid side effects. Then you'd always get the correct view results.
This is step by step code:
return device_db.query('filters/templates')
.then(templates => templates.rows) // Take only the rows into account.
.then(rows => rows.map(row => row.id) // Extract the id. If you wanted the name instead this would be possible with a slightly different view.
// I think it would suffice to log the result right now,
// but if you really want to have a single object with boolean values,
// you can do the following:
.then(ids => ids.reduce((asObject, id) => { // Use Array.reduce() here to avoid any potential side effects.
asObject[id] = true;
return asObject;
}, {})
.then(asObject => { console.log(asObject); }; // Debug the result.
Or more concise with ES2015+:
return device_db.query('filters/templates')
.then(({rows}) => rows.reduce((acc, {id}) => ({...acc, [id]: true }), {}))
.then(result => console.log(result))
By the way: You could also use other strategies to "filter" your documents, as it's not necessary to emit the _id. Instead you can use the key and/or value for "secondary indexes":
{
"_id": "_design/docs",
"views": {
"byType": "function(doc) { emit(doc.type); }",
"templatesByName": "function(doc) { if (doc.type === 'template') emit(doc.name); }",
"byTypeAndName": "function(doc) { emit([doc.type, doc.name], doc.name); }
}
}
you can use docs/byType as an universal view for other doc types too. Just call it with db.query('docs/byType', { key: 'template' })
If you want the templates sorted by name, use db.query('docs/templatesByName') or db.query('docs/byTypeAndName', { startkey: ['template'], endkey: ['template', {}]}).
A word of caution: This is all untested and just from memory, so some brackets might be missing in the code, or some bugs might hide in there.
It's not a bug in PDB, it's about outdated unfortunately components in pouchdb-react-native.
Confirmed way is to combine pouchdb-react-native yourself like this - then queries work as expected:
import PouchDB from 'pouchdb-core';
PouchDB
.plugin(require('pouchdb-adapter-asyncstorage').default)
.plugin(require('pouchdb-adapter-http'))
.plugin(require('pouchdb-mapreduce'))
.plugin(require('pouchdb-replication'))
.plugin(require('pouchdb-authentication'));
const localDB = new PouchDB(localDBname, {adapter: 'asyncstorage', auto_compaction: true});
This way one can be sure that all components are the latest.

Firebase query of child key

I have the next structure in my firebase database:
{
events: {
event_1:{
startdate: 120934210,
enddate: 120934211,
members: {
uid_1: true,
uid_2: true,
...
}
},
event_2:{
startdate: 120934210,
enddate: 120934211,
members: {
uid_2: true,
uid_3: true,
...
}
},
...
}
}
I have a node event and every child is an event, each event have a list of members. The question is, how can I do a query for all events of a certain member? For example, all events with member uid_2. I'm using angularfire2 and angular 4. I'm trying to do something like that:
db.list('events/',ref => ref.orderByChild('members').equalTo(uid))
please help me.
Your current data structure allows you to easily find the members of a specific event. If does not allow you to easily determine the events for a specific user. To allow that, you should add an inverted data structure.
It is also recommended to not nest different entity types, but instead store them in top-level nodes.
For your data this leads to four likely top-level nodes:
users: {
$uid: { ... }
},
events: }
$eventid: { ... }
},
event_users: {
$event_id: {
$uid: true
}
},
user_events: {
$uid: {
$event_id: true
}
}
Now you can easily read (without querying) the members for an event, and the events for a user.
I also recommend you check out:
This great article on NoSQL data modeling
Many to Many relationship in Firebase
http://stackoverflow.com/questions/40656589
The Firebase for SQL developers video series

Firebase two-way relationships / Retrieving data

I would like some explanations about data retrieving in firebase. I'm not an expert in NoSql data structure and something is missing in my mind. It's not natural for me. However, I think i've understood the basics of Two-way relationships in this kind of structure.
Here is my data structure :
{
// Two-way relationships between accounts & logements
"accounts" : {
"uniqueId1" : {
"nom" : 'My name 1',
"email" : 'My email 1',
"logements" : {
uniqueIdLogement1 : true,
// do I have to list relationships with images & geofire here ?
uniqueIdLogement2 : true
},
"favorites" : {
uniqueIdLogement2 : true,
uniqueIdLogement25 : true,
uniqueIdLogement32 : true
}
},
....
},
// Each logement has his own "images" & "geofire" data
"logements" : {
"uniqueIdLogement1" : {
"nom_du_logement" : 'My logement name 1',
"accounts" : {
uniqueId1 : true
},
"images" : {
uniqueIdImages1 : true,
uniqueIdImages2 : true,
uniqueIdImages3 : true,
},
"geofire" : {
uniqueIdGeofire1 : true
},
},
...
},
"images" : {
"uniqueIdImages1" : {
"image" : 'My image URL 1',
"logements" : {
uniqueIdLogement1 : true
}
},
...
},
"geofire" : {
"uniqueIdGeofire1" : {
"g" : 'My geofire Data,
"l" : {
0 : '-44.34',
1 : '-3.2'
},
"logements" : {
uniqueIdLogement1 : true
}
},
...
}
}
I think everyone has his own point of view about data structure but in my opinion (in reference of Firebase Docs) it has to be quite like that. But if you see some updates, don't hesitate !
So, In angularJs, i would like to list each "logements" for account with "uniqueId1" for exemple and display their own "images" & geofire data (and check if they are my user's favorites logements). Is it possible to do that ?
// list every logements for account ID1
// for each logement take images data & geofire data & check if favorites
// push infos in $scope.items & display with ng-repeat
Another question relative to that : When i remove a "logements" for User ID1, i want to remove also all images & geofire references ... ! Is it possible to do that too ?
// remove uniqueIdLogement1 from account ID 1
// remove images where "logements" = uniqueIdLogement1
// remove goofier where "logements" = uniqueIdLogement1
I think that if i understand that correctly, it will be okay for me ! I just can't see right now how it works and it's frustrating because i know there is a lot of potential with this kind of database. Can you explain me please some details about that . Thank you very much
With any database you'll often need to join data from multiple locations to build your view.
In relational databases, you can get the data from these multiple locations (tables in that case) with a single statement, by using a JOIN clause.
In Firebase (and many other NoSQL databases) there is no built-in way to join data from multiple locations. So you will have to do that in your code.
var ref = firebase.database().ref();
var accountRef = ref.child('accounts/uniqueId1');
accountRef.on('value', function(accountSnapshot) {
var logementCount = accountSnapshot.child('logements').numChildren;
var logementLoadedCount = 0;
accountSnapshot.child('logements').forEach(function(logementKey) {
var logementRef = ref.child('logements').child(logementKey.key);
logementRef.once('value', function(logementSnapshot) {
var logement = logementSnapshot.val();
logementLoadedCount = logementLoadedCount + 1;
if (logementLoadedCount == logementCount) {
console.log('We've loaded all logements');
}
});
});
});
Many developers from a SQL and traditional web development background worry about the performance of all those nested calls. In Firebase that is not needed (for reasonable amounts of data), since Firebase pipelines the requests over a single connection. See Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly.
In general I highly recommend reading this article on NoSQL data modeling for a good introduction to the general topic.

Firebase: How do I retrieve records from my data for which a specific key exists?

I have data in firebase that looks like this:
"application": {
"companies": {
"firebase": {
"creation": {
"name": "Firebase Inc",
"location": "USA"
},
"google": {
"creattion": {
"name": "Google Inc",
"location": "USA"
}
}
"facebook": {
},
"apple": {
}
}
}
}
There are tens of thousands of records under companies key. How do i efficiently execute following queries?
How do I query only the records for which key creation is present under their name?
How do I query only the records that DO NOT have key creation present under their name?
I also want to call .on('child_added') on the returned result set so that I can process only those specific records later on. Is it possible?
EDIT: Simpler way without using an extra parameter
Queries
Here are the queries to do this without having to use an extra parameter:
Find the companies without creation:
var ref = new Firebase(fbUrl+'/companies').orderByChild("creation").equalTo(null);
Find the companies with creation:
var ref = new Firebase(fbUrl+'/companies').orderByChild("creation").startAt(!null);
You would add ".indexOn": "creation" to the rules.
Edit 2: I was curious, so I pushed 11,000 records to /companies2 (half with creation children, half without). I was able to retrieve 5500 matching records in ~4 seconds using the above queries (or one of the variants I've shown below).
Edit 3: If you're running these queries frequently, it might be worth it to separate children of /companies into two bins based the presence of creation. That way, you can read the two segments separately without having to rely on queries.
Factory
Here is what the revised factory would look like (I've revised the PLNKR to match):
app.factory("CompaniesFactory",function($q, fbUrl){
return function(hasCreation){
var deferred = $q.defer();
var ref = new Firebase(fbUrl+'/companies').orderByChild("creation");
var query;
if (hasCreation) {
query = ref.startAt(!null);
// or:
// query = ref.startAt(true);
} else {
query = ref.equalTo(null);
// or:
// query = ref.endAt(!null);
// query = ref.endAt(true);
}
query.once("value", function(dataSnapshot){
deferred.resolve(dataSnapshot.val());
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
}
});
And yes, it is possible to call .on('child_added') on the returned dataSnapshot. See DataSnapshot.ref().
Original answer using an extra parameter:
(Keeping this for reference)
Another way to do it would be by adding another parameter called hasCreation to children of companies that have creation, and query by that.
Data
The query would then be var ref = new Firebase(fbUrl+'/companies').orderByChild("hasCreation").equalTo(hasCreation);
If hasCreation in the query is null, the query will return the companies without a hasCreation child.
If hasCreation in the query is true, the query will return the companies with hasCreation===true.
{
"company1" : {
"creation" : {
"name" : "company1"
},
"hasCreation" : true
},
"company2" : {
"name" : "company2"
},
"company3" : {
"name" : "company3"
},
"company4" : {
"creation" : {
"name" : "company4"
},
"hasCreation" : true
}
}
Rules
You would add the ".indexOn" : "hasCreation" to your rules like so:
"so:29179389":{
".read" : true,
".write" : true,
"companies" : {
".indexOn" : "hasCreation"
}
}
Companies Factory
app.factory("CompaniesFactory",function($q, fbUrl){
return function(hasCreation){
var deferred = $q.defer();
if (!hasCreation) {
hasCreation = null;
}
var ref = new Firebase(fbUrl+'/companies').orderByChild("hasCreation").equalTo(hasCreation);
ref.once("value", function(dataSnapshot){
deferred.resolve(dataSnapshot.val());
});
return deferred.promise;
}
});
Controller
app.controller('HomeController',function($scope,fbUrl,CompaniesFactory) {
$scope.getCompanies = function(hasCreation) {
var companies = new CompaniesFactory(hasCreation).then(function(data){
console.log(data);
$scope.companies = data;
});
}
});
HTML
<body ng-app="sampleApp">
<div ng-controller="HomeController">
<button ng-click="getCompanies(true)">Find with creation</button>
<button ng-click="getCompanies(false)">Find without creation</button>
<h2>Companies:</h2>
{{companies}}
</div>
</body>
What I would do, is I would set a condition to verify if your xxx.firebaseio.com/Application/companies/______/creation exists. In a empty blank, you can set for loop to irritate over the array of companies.Then, you can create two arrays with angular.forEach: one including those companies which do have 'creation', and the other array in which the elements do not include the 'creation'.
Hope that helps :)
Edit:
There is another approach to this question, in this thread:
Angularfire: how to query the values of a specific key in an array?

Categories

Resources