How would you model a collection of users and friends in Firebase? - json

I'm trying to create a database (json) with Firebase.
I searched the docs and the net but couldn't find a clear way to start.
I want to have a database of users.
each user (represented as UID) should have a nickname and a list of friends.
I tried making a .json file that looks like this:
{
users:{
}
}
and adding it to the Firebase console to get started but it wouldn't work.
How can I do it?
the database should look like this:
{
users:{
UID:{
nickname: hello
friends: UID2
}
UID2:{
nickname: world
friends: UID
}
}
I don't know if I got that right, so I would really appreciate any help you guys could give me at this subject.
Thanks in advance!

Seems like a good place to start. I would make two changes though.
keep the list is friends separate
keep the friends as a set, instead of a single value or array
keep the list is friends separate
A basic recommendation when using the Firebase Database is to keep your data structure shallow/flat. There are many reasons for this, and you have at least two.
With your current data structure, say that you want to show a list of user names. You can only get that list by listening to /users. And that means you don't just get the user name for each user, but also their list of friends. Chances that you're going to show all that data to the user are minimal, so that means that you've just wasted some of their bandwidth.
Say that you want to allow everyone to read the list of user names. But you only want each user to be able to read their own list of friends. Your current data structure makes that hard, since permission cascades and rules are not filters.
A better structure is to keep the list of user profiles (currently just their name) separate from the list of friends for each user.
keep the friends as a set
You current have just a single value for the friends property. As you start building the app you will need to store multiple friends. The most common is to then store an array or list of UIDS:
[ UID1, UID2, UID3 ]
Or
{
"-K.......1": "UID1"
"-K.......5": "UID2"
"-K.......9": "UID3"
}
These are unfortunately the wrong type for this data structure. Both the array and the second collection are lists: an ordered collection of (potentially) non-unique values. But a collection of friends doesn't have to be ordered, it has to be unique. I'm either in the collection or I'm not in there, I can't be in there multiple times and the order typically doesn't matter. That's why you often end up looking for friends.contains("UID1") or ref.orderByValue().equalTo("UID1") operations with the above models.
A much better model is to store the data as a set. A set is a collection of unordered values, which have to be unique. Perfect for a collection of friends. To store that in Firebase, we use the UID as the key of the collection. And since we can't store a key without a value, we use true as the dummy value.
So this leads to this data model:
{
users:{
UID:{
nickname: hello
}
UID2:{
nickname: world
}
}
friends:{
UID:{
UID2: true
}
UID2:{
UID: true
}
}
}
There is a lot more to say/learn about NoSQL data modeling in general and Firebase specifically. To learn about that, I recommend reading NoSQL data modeling and watching Firebase for SQL developers.

I keep a collection of Friends where the users field is an array of 2 user ids: ['user1', 'user2'].
Getting the friends of a user is easy:
friendsCollection.where("users", "array-contains", "user1").get()
This should get you all documents where user1 appears.
Now the tricky part was on how to query a single friend. Ideally, firebase would support multiple values in array-contains, but they won't do that: https://github.com/firebase/firebase-js-sdk/issues/1169
So they way I get around this is to normalize the users list before adding the document. Basically I'm utilizing JS' truthiness to check what userId is greater, and which is smaller, and then making a list in that order.
when adding a friend:
const user1 = sentBy > sentTo ? sentBy : sentTo
const user2 = sentBy > sentTo ? sentTo : sentBy
const friends = { users: [user1, user2] }
await friendsCollection.add(friends)
This basically ensures that whoever is part of the friendship will always be listed in the same order, so when querying, you can just:
await friendsCollection.where("users", "==", [user1, user2]).get()
This obviously only works because I trust the list will always have 2 items, and trust that the JS truthiness will work deterministically, but it's a great solution for this specific problem.

Related

Is there a good way to get children in MySQL as an array?

First of all, apologies if this is a duplicate, I don't really know the best terminology for this question.
Say I have SQL schema that represents:
Businesses:
id: int
name: string
Employees:
id: int
business_id: int REFERENCES Businesses(id)
name: string
Is there any conceivable way I could query for a list of businesses each with an array of employees attached as a field? Currently I am pulling the list of businesses into my server, and then for each business in the array, getting all employees who have a business_id equal to its id. This works, but for a large number of businesses, it results in a high number of queries to the database.
Another option I considered was reading all employees into memory on my server, putting them into a map of [business_id->[employees]], and then sending the employees array to the right businesses employees field on the server. This seems to be a much more performant option, but I wanted to know if there is anything built into MySQL that would allow me to do this out of the box, as it seems like a pretty common task. Thank you!
EDIT:
#MatBailie made a point about code smells here, and I just wanted to clarify the total intent of this code. I will add a step by step walkthrough of what it would do from a high level.
Select all companies from a list
Get all employees of each company
Display to a user a list of companies, and next to each one, a scrollable list of each employee
I understand, as #radocaw pointed out, that getting a list of employees for a business is a trivial join, but my issue is that this will take numerous trips to the database. I will post my current method of solving this issue in Golang below, using two simple SELECTs from MySQL and the use of maps. This is for you, #Tangentially Perpendicular!
employeeMap := make(map[int][]Employee)
employees,err := db.Query("SELECT business_id,f_name,l_name,email FROM employees")
if err != nil {
//handle error
}
for employees.Next(){
var employee Employee
err = employees.Scan(&employee.BusinessId,&employee.FName,&employee.LName,&employee.Email)
if err != nil {
//handle error
continue
}
if _,ok := employeeMap[employee.BusinessId];ok{
employeeMap[employee.BusinessId] = append(employeeMap[employee.BusinessId],employee)
}else {
employeeMap[employee.BusinessId] = make([]Employee,1)
employeeMap[employee.BusinessId[0] = employee
}
}
This is used to build a map of business ids to lists of employees. Next I would just get the businesses from the database, and for their id, attach that array of employees to them.
I'm not sure if this is even a good way to do this, but it seems more optimized and direct than hitting the database with n joins, where n is the number of businesses. Thanks everyone for the great feedback and high quality answers/comments!
There are several subtly different questions here.
The first is:
Is there a good way to get children in MySQL as an array?
Answer: There may be a way - but it cannot be 'good'.
The second is:
Is there any conceivable way I could query for a list of businesses each with an array of employees attached as a field?
Answer: In a normalised environment, it's inconceivable that a list of businesses would have 'an array of employees attached as a field'
The third might be:
In a normalised environment, how might I pull the a list of businesses into my server and then, for each given business, obtain a list of all employees of that business.
Answer: This is a trivial exercise, normally employing JOINs. For further help, see https://stackoverflow.com/help/minimal-reproducible-example
You can achieve something close by returning a CSV list using group_concat:
SELECT b.id,b.name,group_concat(e.id) employee_ids, group_concat(e.name) employee_names
FROM businesses b
LEFT JOIN employees e on b.id = e.business_id
GROUP BY b.id,b.name
And then subsequently splitting the values into arrays in code.

Data structure for like/favorite in social blogging app

What's the best way to save data in db to keep track of a user clicked a like/favorite button or not?
I've tried the following data structure:
postId:{
...
like_count:123,
user_who_liked:[uid1,uid2,uid3...]
}
When the data above is downloaded to the client, we can check if the client liked the post or not by checking if the array user_who_liked contains the client's uid or not. However, if there are more than 1000 items in the user_who_liked array, this approach consumes too much redundant bandwidth.
I found the following json data from instagram:
{
...
viewer_has_liked: false
viewer_has_saved: false
viewer_has_saved_to_collection: false
viewer_in_photo_of_you: false
}
This approach seems more efficient, but how do the database know if the user has liked or not? Do they store a bunch of uids and check if the user's uid is inside it? Are those has_liked/has_saved booleans derived attributes?

Get array of friend names with friend id's from MongoDB

I'm making MEAN app for the first time. I managed to insert users into MongoDB. Each user looks like:
{
"name":"My Name",
"country":"Italy",
"friends":"5922c66b4e708e27a02b7937"
}
My first question is, how to manualy insert more than one friend (how to separate them via Postman)?
Then I have html page to show those informations about user, so I display his friends that way:
<td>{{user.userFriends}}</td>
When I will have more than one firend in database, how to display number of friends instead of their IDs?
Thanks for help!
EDIT: Solved the first part with
{
"friends":["5922c66b4e708e27a02b7937","5922c66b4e708e27a02b7938"]
}
To get Mongo to calculate the number of friends a user has, you can use the $size operator on the friends array. You need to use the aggregation pipeline to do this though. Otherwise, you can just calculate the length of the array yourself. For more details on the $size operator and the aggregation pipeline, see here: https://docs.mongodb.com/manual/reference/operator/aggregation/size/

Firebase EqualTo performance & security

I have an array of objects related to users and want to get all objects related to one user. I can't save the userid as a parent node but as a child so that I want to use the equalTo method.
ref.orderByChild("userid").equalTo(uid).on("child_added", function(snapshot) {
console.log(snapshot.val());
});
Does this first query all objects (slow) and then select only the required ones or does firebase optimize the query itself on the server? I come from SQL and I am a bit unsure how to handle where queries in firebase.
Edit: there are also security issues. A user could receive all objects by hacking the js code? I hope the security rules should solve this?
Example JSON:
{
Objectkey1: { userid: 'uid', ... },
Objectkey2: { userid: 'uid', ... },
...
}
Does this first query all objects (slow) and then select only the required ones or does firebase optimize the query itself on the server?
Yup, that's pretty much what happens. So this operation will always get slower as you add more items to the location identified by ref.
If that type of performance is a concern (i.e. if you care about scalability), consider adding an inverted/secondary index to the thing that user identified by uid.

Laravel 4 Eager Loading filtering and selecting only matching results

I'm trying to output the filter results with only matching elements.
I have two tables (in the real project, which will be 5), let's say companies and projects. A company may have more than one project or may not have any.
These are the relations:
/app/models/Company.php
<?php
class Company extends Eloquent {
public function projects() {
return $this->hasMany('Project','companyID');
}
protected $table = 'companies';
}
/app/models/Project.php
<?php
class Project extends Eloquent {
public function companies() {
return $this->belongsTo('Company','companyID');
}
}
What I want to do is, I want to get results of them both but only with matching parameters.
I've tried this:
return Company::with(array('projects'=>function($query){
$query->where('id',99); //project's id is 99
}))->get();
This is the output JSON
If I change the value from 99 to 1 (there is a result with products.id of 1), it changes into this:
I only want to get the second result from the second JSON i've posted.
As you can see in the second JSON (I'm using this parser to check), all companies are loaded regardless of the project, and only the rows matched have the object projects.
There will be more 'with's and I don't know how to filter only matching elements.
I also tried having() inside closure, but it's still same:
$query->having('projects.id','=',99);
Is there a way to filter only matching results (without using a loop) which the output will only include the results having the matched projects object?
Edit: Actually, 5 tables will be filtered.
companies, projects, works, users and user_works
Let's say;
"Companies" have many projects.
"Projects" have many works
"Works" have many users, also "Users" may have more than one work (pivot table user_works).
All relations are set correctly from models.
I want to do a global searching to these.
Something like: "Bring me the user id 1's works which has company id of 5 and project id of 4", but none of the fields are mandatory.
So these are also valid for searching: "Bring me everyone's works on project id of 2", or "bring me id 2's works", or "bring me all the works starting from today", "bring me the id 1's works on project 2", "Bring me this year's works done of company id 1".
Thanks in advance
Using whereHas() is the solution on my case. It filters relation and affects the results returned in the main query.
If you have the id of the project, would it make more sense to go that route? $project = Project::find(99); and then the company variables would be accessible with $project->companies->name;
It would make more sense to rename the companies() function to company() because a project will only ever belong to one.