For a small app prototype I'm building, I would like to use firestore (firebase) to store some data. I'm wondering if the following is a good way of going about a nosql database.
I have Paths, that belong to Categories. A Path can have courses and comments. I would like users to see the likes a path has, and get categories sorted by the amount of paths inside.
That's why I'm adding the paths_count on the categories table, I will use Cloud Functions to update the counts for the likes and paths on each database update.
categories: [
1: {name: "productivity", paths_count: 10},
2: {name: "cooking", paths_count: 5},
]
paths: [
1: {
name: "Productivity 101",
category_id: 1,
likes_count: 5,
likes: [],
courses: [],
comments: []
}
]
Is this a good start?
Related
I'm building a task management app in firestore. Tasks can have multiple members and tags. Since I would always sort and display content (based on due-date, priority, etc.) of user(s) and due to the limitations of firestore with lists and composite indexes, I have ended up storing data in the following structure.
projects:
.....
101: {
name: 'task1',
members: {201: true, 202: true, ......},
tags:{'tag1':true, 'tag2':true, 'tag3':true,....}
},
102: {
name: 'task2',
members: {201: true, 202: true, ......},
tags:{'tag1':true, 'tag2':true, 'tag3':true,....}
},
103: {
name: 'task3',
members: {201: true, 202: true, ......},
tags:{'tag1':true, 'tag2':true, 'tag3':true,....}
}
.....
Now, since composite indexes have to be manual, I ended up implementing reverse lookup:
users:
.....
201: {
name: 'John',
tasks:
501: {taskId: 601, priority: high, ...... },
502: {taskId: 601, priority: high, ...... },
503: {taskId: 601, priority: high, ...... },
}
202: {
name: 'Doe',
tasks:
504: {taskId: 601, priority: high, ...... },
505: {taskId: 601, priority: high, ...... },
506: {taskId: 601, priority: high, ...... },
}
......
At this point, if you have to filter tags too, Under users, I will have to add subcollection for each tag and store tasks under them too. This will create insane amount of documents for each task. For example, if you have one task with 3 members and 3 tags, this setup will create 12 documents for just one task. And any changes I make will involve 12 writes.
What am I missing here? Is it the way I'm storing the data? or is it more to do with the lack of capabilities of firestore itself?
Firestore does have limitations on querying
What I see is that you are trying to normalize the data, that is a good approach if you are using RDBMS. In Firestore usually it is recommended to denormalize the data as much possible. But again its a trade-off, your read will be fast & easy to query but write might be slow since you might have to write data at multiple places.
Denormalizing - In simple words, its having flat data structure
Good read -
https://angularfirebase.com/lessons/firestore-nosql-data-modeling-by-example/
I'm trying to implement a Reddit/HackerNews style tree of comments as part of a project, and am trying out Firestore as a database solution. However, I'm unsure as to the correct design reading through the docs. In a SQL database I would use numeric keys like:
0
1.0
1.1
1.1.1
0.0
to represent my tree. However, numeric keys like that seem to be a Firebase antipattern. The other route is using an actual tree in the json where a post is represented like:
{
uid: 'A0000',
content: 'foo',
children: [
{uid:..., content:..., children: []}]
}
but supposedly deep trees are bad in Firestore. As I understand it the reason deep trees are bad is that you have to fetch the whole thing, but in my case I'm not sure if that's a problem. A client fetching a post would fetch the root content node and the first 20 or so child trees. That could be a pretty big fetch, but not insanely so.
Does anyone know of a good standard way to implement this kind of structure?
Extra: Here is the more verbose expression of what the structure should look like once the client processes it.
{
uid: 0,
title: 'Check out this cat!',
body: 'It\'s pretty cute! This **text** is [markdown](link), so it can have ' +
'links and *stuff*. Yay!',
poster: {
uid: 0,
name: 'VivaLaPanda',
aviUrl: 'badlink',
},
posted: '2018-03-28',
children: [{
uid: 0,
body: 'This is a comment, it\'s angry!',
poster: {
uid: 0,
name: 'VivaLaPanda',
aviUrl: 'badlink',
},
posted: '2018-03-20',
children: [{
uid: 0,
body: 'This is a comment, it\'s neutral!',
poster: {
uid: 0,
name: 'Steve',
aviUrl: 'badlink',
},
posted: '2018-03-20',
children: [{
uid: 0,
body: 'This is a comment, it\'s neutral!',
poster: {
uid: 0,
name: 'Craig',
aviUrl: 'badlink',
},
posted: '2018-04-10',
children: []
}, ]
}, ]
},
{
uid: 0,
body: 'This is a comment, it\'s happy!',
poster: {
uid: 0,
name: 'Craig',
aviUrl: 'badlink',
},
posted: '2018-03-28',
children: []
},
]
};
Edit:
While I've marked this as answered because there is an answer, I'm still really interested in seeing something more elegant/efficient.
Edit2:
For posterity: I ended up deciding that any Firebase solution was hopelessly convoluted and just used DGraph for the data, with Firebase sitting in front for Auth.
This is tough since the structure you have is naturally recursive. The obvious options are each comment is a new document in a collection and each reply is a single document in the same collection.
Each comment as a new document could work something like this. Each comment has a "postId" attribute which dictates which post it belongs to. Some comments, those which are replies to other comments, have a "replyToId". These two attribute in conjunction allow your client app to:
Get the top level comments (look for comments with the correct postId and which don't have replyToId). Top level comments allows you to limit the size of payloads if you need to worry about that in the future.
Get all comments (look for comments with the correct postId only). If you don't care about payload sizes you can get everything and figure out the tree structure on the client.
Get replies to a particular comment if you want "see replies" YouTube style comments interaction (looks for comments which have a particular replyToId). This works well in conjunction with 1. for limiting payload sizes.
But the logic here is obviously complex.
Your children approach could be really messy if there are a lot of people commenting on eachother. A nicer approach would be the following structure for every comment:
// single comment
postUid // <- random generated by firebase
{
postedBy: userUid
postedTime: timestamp
postIsChildOfUid: postUid // <- reference to an other post (optional if the comment didn't respond to another comment(top-level comment))
}
This doesn't even require nesting at all :). You can generate easily now a comment tree with this approach, but this has to be client side. But that should be easy!
I am learning firebase and trying to find the best way to structure my data.
Use an example of a simple leave application. Employees can submit and view their leaves. Managers can approve leaves.
Option 1
"leaves": [
{
"employee": "pCIUfttSrXQ1dLPDwH7j9GExCkA2",
"date": "2017-03-01",
"status": "pendingApproval",
},
{
"employee": "YSJCAe4wZdYCplA3e0ejMqzQmEF3",
"date": "2017-01-01",
"status": "approved"
}]
With option 1, filtering will be required in both cases:
When employee lists his leave history (filter by "employee")
When manager lists all the pending leaves (filter by "status=pending")
Option 2
"leaves":
{
"pCIUfttSrXQ1dLPDwH7j9GExCkA2" : [
{
"date": "2017-03-01",
"status": "pendingApproval"
}
],
"YSJCAe4wZdYCplA3e0ejMqzQmEF3" : [
{
"date": "2017-01-01",
"status": "approved"
}
]
}
With option 2, no filtering is required when employee lists his leave history, but filtering is required (and I don't know how) for manager to list pending leaves.
What should be the right way to structure the data? And if it's option 2, how would we filter the pending leaves for all employees?
Use the second option;
For the manager to filter through the pending queries , use:
FIRDatabase.database().reference().child("leaves").queryOrdered(byChild: "status").queryEqual(toValue: "pending").observeSingleEvent(of: .value, with: {(Snapshot) in
print(Snapshot.value!)
// If you have multiple pending request you gotta loop through them
// using for loop, access them as separate entity and operate on them
})
I have the following Mongo collection:
{
id: '123456',
name: 'GameXYZ',
reviews: [
{createdBy: 'Bob', score: 5}, {createdBy: 'John', score: 8}
]
}
I would like to create a publish function that returns only the review created by Bob:
{
reviews: [
{createdBy: 'Bob', score: 5}
]
}
I've tried this:
return myCollection.find({'reviews.createdBy': 'Bob'}, {'reviews.$': 1});
The problem is Meteor returns the entire document. According to their documents, "Field operators such as $ and $elemMatch are not available on the client side yet."
My function is running on the server, so I don't know why it's not working. It does work on the Mongo Shell.
My question is: could anyone recommend a way to publish only that single object of the array, in Meteor?
This is best done like the comments example from discover meteor. Create new collection reviews. You can put what ever you need in it but it has to have the id of what ever it's a review of. That way you can publish and find it with reviews.find({reviewsId: the id of the collection; in this case 123456})
I'm having a hard time trying to get data about a person from Freebase using his social link - by a MQL query.
How could this be done?
Something like:
https://www.googleapis.com/freebase/v1/mqlread?query={
"*":[{}],
"/common/topic/social_media_presence":[{
"value":"http://twitter.com/JustinBieber"
}]
}
Those links are really stored as keys and the links are generated from templates with they key plugged in. You can see all the keys here: https://www.freebase.com/m/06w2sn5?keys=
A modified version of your query would be:
[{
"key": [{
"namespace": {
"id": "/authority/twitter"
},
"value": "JustinBieber"
}],
"*": [{}]
}]
You can do the same thing with other namespaces like /authority/facebook or /authority/musicbrainz as well as the various language wikipedias e.g. /wikipedia/en
I'm not sure how complete the coverage or currency of the social media info is though...