Firebase querying grandchildren / multilevel querying - firebase

From everything I have read, it doesn't seem possible to query a multilevel value.
My data structure looks like the following:
{
"dinosaurs": {
"bruhathkayosaurus": {
"meta":{
"addedBy":"John",
"addedDate":"02021987"
},
"appeared": -70000000,
"height": 25
},
"lambeosaurus": {
"meta":{
"addedBy":"Peter",
"addedDate":"12041987"
},
"appeared": -76000000,
"height": 2.1
}
}
}
Without knowing the key name of the dinosaurs, is there anyway to query the meta node retrieving only items added by John.
In JS Something like:
var ref = new Firebase('test.firebaseio.com/dinosaurs');
ref.orderByChild("meta/addedBy")
.equalTo('Peter')
.on("child_added", function(snapshot) {
console.log(snapshot);
});
There are hacky solutions but none are scaleable, should I just flatten this data?
Edit:
I need a code review... would this be an acceptable solution?
var ref = new Firebase('test.firebaseio.com/dinosaurs');
ref.orderByChild("meta")
.on('child_added',function(snap1){
snap1.ref().orderByChild("addedBy")
.equalTo("Peter")
.on('child_added', function(snap2) {
console.log(snap2.val());
})
});

Edit Jan 2016: Since this answer, Firebase has Deep Queries so you can query deeper than 1 level.
Queries can only be 1 level deep. There are a number of solutions but flattening your data and linking/referencing is an option.
In the example above you could create another node that links the user names (parent) to the dinosaurs (children) they added. Then John node can be read and immediately know which dinosaurs he added. Then be able to access other relevant data about that dino; date added, appeared,height etc.
users
John
bruhathkayosaurus
Styracosaurus
Lambeosaurus
Spinosaurus
Peter
Lambeosaurus
Seismosaurus
You will probably want to use uid's instead of names but you get the idea.
Also, it's not clear why there is a meta node in the example listed so it could be flattened thusly:
"dinosaurs": {
"bruhathkayosaurus": {
"addedBy":"John"
"addedDate":"02021987"
"appeared": -70000000
"height": 25
},

Related

Best way to make ReactiveAggregate reactive when data changes on a user

I am currently using ReactiveAggregate to find a subset of Product data, like this:
ReactiveAggregate(this, Products, [
{ $match: {}},
{ $project: {
title: true,
image: true,
variants: {
$filter: {
input: "$variants",
as: "variant",
cond: {
$setIsSubset: [['$$variant.id'], user.variantFollowing]
}
}
}
}}
], { clientCollection: 'aggregateVariants' }
As you can see, a variant is returned if user.variantFollowing matches. When a user 'follows' a product, the ID is added to their object. However, if I understand correctly, this is not triggering ReactiveAggregate to get the new subset when this happens. Only on a full page refresh do I get the correct (latest) data.
Is this the correct way to approach this?
I could store the user's ID as part of the Product object, but the way this would be stored would be nested two places, and I think I would need the Mongo 3.5 updates to then be able to accurately update this. So i'm looking for how to do this in Meteor 1.5+ / Mongo 3.2.12
So, I've been able to get there by adding autorun to the subscription of the aggregate collection, like this:
Template.followedProducts.onCreated(function() {
Meteor.subscribe('products');
this.autorun(() => {
Meteor.subscribe('productsFollowed');
});
... rest of function
For context, productsFollowed is the subscription to retrieve aggregateVariants from the original question.
Thanks to robfallows in this post: https://forums.meteor.com/t/when-and-how-to-use-this-autorun/26075/6

How to do Complex Querying with Firebase?

I'm creating my first app in Firebase. I have no experience with NoSQL, so working out my data structure is proving to be a challenge. Let's say my app is similar Reddit where users visit the site and read/write posts. I want the app to have a list view where it sorts the post data in several ways, however it is all centered around the date posts where submitted:
Views
Show the latest posts in descending order.
Show the latest posts for a specific tag.
Show the most liked posts in descending order for the last day (24 hours).
I assume the data structure to look this:
{
"posts": {
"post_0": {
"content": "...",
"created_at": 1497112445748,
"likes": 100,
"tags": {
"tag_0": true,
"tag_2": true
}
},
"post_1": {
"content": "...",
"created_at": 1497112549374,
"likes": 30,
"tags": {
"tag_1": true
}
},
"post_2": {
"content": "...",
"created_at": 1497112640376,
"likes": 70,
"tags": {
"tag_1": true,
"tag_2": true
}
},
...
}
}
View 1
This is probably the easiest to resolve. I imagine the script to retrieve the data would be something like this:
const ref = firebase.database().ref("posts");
const query = ref.orderByChild("created_at").limitToLast(50);
query.on("child_added", (snapshot) => {
// Do stuff like add to array for sorting
});
View 2
This is where things get tricky. Since you can only have one orderBy* per query, the only way I can see to pull this off is to have a tags node that duplicates the date and post ID. For example:
{
"tags": {
"tag_2": {
"post_0": {
"created_at": 1497112445748
},
"post_2": {
"created_at": 1497112640376,
}
},
...
}
}
I've read this is the whole concept of denormalization and structuring your data around your views, but isn't there a better way?
View 3
I don't know how to solve this one at all. As the last 1 day is changing every time the view is requested and the likes are fluctuating often, how can I possibly structure my data around this view?
I've read that push keys, which would take place of the post_n key I have in my example, are sequential and can somewhat be relied on as a timestamp. I'm not sure if there's some way to take advantage of that.
I've found a few useful videos by the Firebase team and articles on Medium, but I'm afraid they don't go far enough for me to understand how to accomplish the needs of my app.
Common SQL Queries converted for the Firebase Database
Firebase Data Structures: Pagination
I'm just find this aspect of Firebase really confusing to get my head around to have it return the data I need for my views.
If anybody can provide me with an example of how to accomplish these things, it would be much appreciated! Thanks!

4 level subscription nesting in meteor

I am using meteor and this is my schema, each is a separate collection:
Courses has many lectures
Lectures have many questions
Questions have many answers
I want 1 page where I can display a given course's lectures, questions, and answers. I can display a course's lectures no problem but I have issues with displaying further nested items. I'd ideally like to have:
Lecture has courseId
Answer has lectureId (but not courseId)
Question has answerId (but not lectureId or courseId)
Is that wise or should I embed courseIds and lectureIds in all child components? This is my iron router, I tried to extend the same idea that worked with nesting lectures with questions but I hit a stumbling block with how to feed the subscriptions the lecturesId:
Router.route('/courses/:_id', {
name: 'CoursePage',
waitOn: function(){
return [
Meteor.subscribe('singleCourse', this.params._id),
Meteor.subscribe('lectures', this.params._id),
Meteor.subscribe('questions', this.params._id)
];
},
data: function() {
return Courses.findOne(this.params._id);
}
});
This is the subscriptions for the course page, again with my stumbling block of not really knowing how to feed in a lectureId:
Template.CoursePage.helpers({
Lectures: function() {
return Lectures.find({courseId: this._id});
},
Questions: function(lectureId) {
return Questions.find({courseId: this._id, lectureId: lectureId});
}
});
Can anyone recommend a good way to do this 4 level nesting for a single page? I think that I am missing something obvious but I can't quite find a good example with google searching.
Thanks!
You can Publish Composite package for this. See the following sample code and edit as per your collection schemas,
Meteor.publishComposite('singleCourse', function (courseId) {
return [{
find: function() {
return Courses.find({ id: courseId});
}
}, {
find: function() {
return Lectures.find({ courseId: courseId});
},
children: [{
find: function(lecture) {
return Questions.find({ lectureId: lecture.id });
},
children: [{
find: function(question) {
return Answers.find({ questionId: question.id });
}
}]
}}
}]
});
Then in your router, you can simply make one subscription call,
Router.route('/courses/:_id', {
name: 'CoursePage',
waitOn: function(){
return [
Meteor.subscribe('singleCourse', this.params._id)
];
},
data: function() {
return Courses.findOne(this.params._id);
}
});
This is one of the best packages (if not the best) as of now to reactively publish set of related documents from different collections.
There are some known issues while doing these kind of reactive joins but for smaller datasets, this works without any problem.
Hope it helps.
Mongo can support using aggregation. $lookup will let you connect and gather data between your collections like an SQL join.
Using this in meteor requires using an external mongo ($lookup is new as of Mongo 3.2, meteor's Mongo is still 2.6.7) and a package such as the meteorhacks:aggregate package. There are other packages that address this, as mentioned in the comments, aggregate is just what I've used; with it you call Courses.aggregate(...) per the mongo aggregation documentation to produce the data that you require.
In my use, I had a Meteor method defined that took filter parameters as arguments
'aggregateReport':function(filterPersonnel, filterCourse, filterQuarter){
return Personnel.aggregate([{$match: filterPersonnel}, {$unwind: "$courses"},
{$lookup: {from: "courses", localField: "courses", foreignField: "_id",
as: "course_docs"}}, {$unwind: "$course_docs"}, {$match: filterCourse},
{$match: filterQuarter}]);
The Personnel have: country, course date, lastname, fullname, ..., course #, course. (The ellipses covers non-relevant to the query). The above queries Personnel per the filter, spools it out to one record per course (this is a transcript type of view for many people in a program), then adds the information from Courses as course_docs to the returned Personnel, and then filters by course parameters and date parameters. code and dependencies were meteor 1.2; Feb 2016

Meteor Framework Subscribe/Publish according to document variables

I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.

How to query related records in Firebase?

Given this database structure in Firebase:
{
"users": {
"user1": {
"items": {
"id1": true
}
},
"user2": {
"items": {
"id2": true
}
}
},
"items": {
"id1": {
"name": "foo1",
"user": "user1"
},
"id2": {
"name": "foo2",
"user": "user2"
}
}
}
which is a more efficient way of querying the items belonged to a specific user?
The Firebase docs seem to suggest this:
var itemsRef = new Firebase("https://firebaseio.com/items");
var usersItemsRef = new Firebase("https://firebaseio/users/" + user.uid + "/items");
usersItemsRef.on("child_added", function(data){
itemsRef.child(data.key()).once("value", function(itemData){
//got the item
});
});
but using the .equalTo() query works as well:
var ref = new Firebase("https://firebaseio.com/items");
ref.orderByChild("user").equalTo(user.uid).on("child_added", function(data){
//got the item
});
The latter code seems more concise and doesn't require denormalization of the item keys into the user records but it's unclear to me if it's a less efficient methodology (assuming I create an index on "user").
thanks.
This is rather old one, but when working on the firebase-backed app, I found myself dealing with similar issues quite often.
.equalTo is more time-efficient (especially, if one user owns big number of items). Although n+1 subscriptions does not lead to n+1 networking roundtrips to the cloud, there is some performance penalty for having so many open subscriptions.
Moreover, .equalTo approach does not lead to denormalization of your data.
There is a gotcha however: When you'll want to secure the data, the .equalTo approach may stop working at all.
To allow user to call orderByChild("user").equalTo(user.uid), they must have read privilege to 'items' collection. This read permission is valid for the whole sub-document rooted at /items.
Summary: If user1 is to be prevented from finding out about items of user2, you must use the BYOI (build your own index) approach. That way you can validate that user only reads items that are put to their index.
Finally, disclaimer :) I use firebase only for a short period of time all I got is a few benchmarks and documentation. If I'm mistaken in any way, please correct me.

Resources